# Symfony/Bref/Pulumi : ajout d'un nom de domaine et d'une tâche planifiée

## Préambule

Cette article est le deuxième d'une série sur le déploiement d'un projet Symfony sur AWS Lambda avec l'outil d'IaC Pulumi. Dans le [premier article](https://techblog.pandacode.fr/deployer-un-projet-symfony-sur-aws-lambda-avec-pulumi-et-bref), nous avons mis en place le nécessaire pour avoir notre API accessible via une url fournie par AWS ressemblant à ça : `https://0nz9kqsta9.execute-api.eu-west-3.amazonaws.com`

Dans ce deuxième article, nous allons voir comment utiliser un nom de domaine plus *friendly* pour notre API et comment mettre en place une tâche planifiée (*aka cron*): 2 choses essentielles dans tout projet ;)

Comme pour le premier article, le code est disponible sur le [repository Github](https://github.com/popofr13/blog-articles/tree/main/2023-02-pulumi-php-bref-2), et j'ai fait en sorte qu'il y ait une [PR contenant seulement les modifications liées](https://github.com/popofr13/blog-articles/pull/2/files) à ce deuxième article.

## La partie "Infrastructure" : le sous-domaine personnalisé

J'ai acheté le nom de domaine `my-projects.tech` spécialement pour écrire cet article : nous allons faire en sorte que le sous-domaine `api.my-projects.tech` soit utilisé pour notre API.

### Le certificat SSL ([AWS Certificate Manager](https://aws.amazon.com/certificate-manager/?nc1=h_ls))

Avant de pouvoir plugger notre domaine sur notre *API Gateway*, nous avons besoin d'un certificat *SSL* que nous obtiendrons en utilisant un service AWS : celui-ci sera ensuite facilement utilisable avec les autres services.

```typescript
// src/acm/index.ts
import * as aws from "@pulumi/aws";

export const sslCertificateApi = new aws.acm.Certificate("api",
    {
        domainName: "api.my-projects.tech",
        validationMethod: "DNS",
    }
);
```

### Le DNS ([Amazon Route53](https://aws.amazon.com/route53/?nc1=h_ls))

Nous voyons que la méthode de validation du certificat choisie est DNS. Nous allons donc utiliser le service Route53 pour gérer les enregistrements DNS nécessaires à cette validation.

```typescript
// src/route53/index.ts
import * as aws from "@pulumi/aws";
import {sslCertificateApi} from "../acm";
import {apiDomainName} from "../api-gateway";

// La liste des serveurs de nom de domaines
export const mainDelegationSet = new aws.route53.DelegationSet("main", {}, {});

// Création de la Zone principale (domaine)
export const zone = new aws.route53.Zone("my-projects", {
    delegationSetId: mainDelegationSet.id,
    name: "my-projects.tech",
});

// L'enregistrement DNS permettant la validation du certificat SSL
const apiAcmValidation = new aws.route53.Record("api.acm",
    {
        zoneId: zone.zoneId,
        name: sslCertificateApi.domainValidationOptions[0].resourceRecordName,
        type: sslCertificateApi.domainValidationOptions[0].resourceRecordType,
        records: [sslCertificateApi.domainValidationOptions[0].resourceRecordValue],
        ttl: 10 * 60,
    }
);

// Lance la validation du certificat SSL
new aws.acm.CertificateValidation(
    'api',
    {
        certificateArn: sslCertificateApi.arn,
        validationRecordFqdns: [apiAcmValidation.fqdn],
    }
);
```

> **Note :** si vous n'avez pas acheté votre nom de domain chez AWS (ce qui est mon cas =&gt; chez Gandi), il faudra modifier les serveurs de noms pour définir ceux d'AWS. Pour connaître la liste, il faudra préalablement faire un `pulumi up` pour créer la zone et récupérer la liste (DelegationSet dans le code).
> 
> ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1677667355133/de55571f-85b0-473b-9e09-41eb1140a1a0.jpeg align="center")

### Modification de notre API Gateway

Il nous restera une petite modification à faire dans *Route53*, mais nous avons d'abord besoin de modifier notre *API Gateway* pour prendre en compte notre sous-domaine et le mapper à notre *stage*.

```typescript
// src/api-gateway/index.ts
import * as aws from "@pulumi/aws";
import {sslCertificateApi} from "../acm";

export const apiApi = new aws.apigatewayv2.Api("api", {
    protocolType: "HTTP",
});

const apiStage = new aws.apigatewayv2.Stage("api.default", {
    apiId: apiApi.id,
    autoDeploy: true,
    name: "$default"
});

// Ajout de notre sous-domaine personnalisé et utilisant de notre certificat SSL
export const apiDomainName = new aws.apigatewayv2.DomainName("api", {
    domainName: 'api.my-projects.tech',
    domainNameConfiguration: {
        certificateArn: sslCertificateApi.arn,
        endpointType: "REGIONAL",
        securityPolicy: "TLS_1_2",
    },
});

// Mapping de notre sous-domaine avec le stage de notre APIG
new aws.apigatewayv2.ApiMapping("api.api", {
    apiId: apiApi.id,
    domainName: apiDomainName.id,
    stage: apiStage.id
});
```

Il ne manque plus qu'à créer l'enregistrement DNS pour que le sous-domaine `api.my-projects.tech` pointe bien vers notre *API Gateway*.

```typescript
// src/route53/index.ts
// ...

// Création de l'enregistrement DNS qui utilise les informations du nom de domaine défini dans API Gateway (aws.apigatewayv2.DomainName).
new aws.route53.Record('api.apig', {
    zoneId: zone.zoneId,
    name: 'api.my-projects.tech',
    type: "A",
    aliases: [{
        evaluateTargetHealth: true,
        name: apiDomainName.domainNameConfiguration.targetDomainName,
        zoneId: apiDomainName.domainNameConfiguration.hostedZoneId
    }],
});
```

Il n'y a plus qu'à `pulumi up` et notre API sera accessible via notre sous-domaine personnalisé.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1677668068686/f82449e6-9a11-4c2e-8b03-ce78a3f27341.jpeg align="center")

## La partie "code" : la tâche planifiée

Cette partie est très simple car gérée par *Serverless framework* ([doc Schedule](https://www.serverless.com/framework/docs/providers/aws/events/schedule)) et *Bref* ([doc Cron](https://bref.sh/docs/web-apps/cron.html)). Il suffit de modifier le fichier `serverless.yml` pour configurer l'exécution de notre cron.

```yaml
// serverless.yml

    cron1:
        handler: bin/console
        layers:
            - ${bref:layer.php-82}
            - ${bref:layer.console}
        events:
            - schedule:
                  enabled: true
                  rate: cron(*/15 * * * ? *) # l'expression de planification (https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html)
                  input: '"app:cron1 arg1 --option1=foo"' # la commande Symfony
```

Voici un petit extrait des dernières invocations que l'on peut trouver sur la Console Web sur la page de détails de la fonction Lambda (*pour les plus observateurs, la planification n'a pas toujours été toutes les 15 minutes ^^*).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1677668710315/88500789-9537-476d-bb91-352e7490e719.jpeg align="center")

## C'est fini pour aujourd'hui !

Dans le prochain article, nous verrons comment ajouter un serveur SQL (mais *serverless* quand même ^^) à notre infrastructure et comment faire en sorte que nos *Lambda* en *Symfony* y accèdent.

N'hésitez pas à commenter ce post, ou me laisser un message sur Twitter/Linkedin pour toutes remarques de tout type (erreur, avis, envie, question, ...) : j'essaierai d'en tenir compte et d'y répondre rapidement.
