Un petit peu de contexte
Cet article découle d’un besoin client que nous avons régulièrement : ils souhaitent réaliser leurs déploiements via AWS CodePipeline de manière pré-programmée. Par exemple, une mise en production uniquement les lundis à 9H.
Seul petit soucis : cette fonctionnalité n’est pas proposée de manière native sur AWS CodePipeline ! Nous avons donc mis au point un petit stratagème que nous partageons avec plaisir avec vous.
C’est parti !
L’architecture retenue
Bonne nouvelle : l'architecture retenue est assez basique ! Il nous suffit d’ajouter une étape dans notre pipeline juste avant le déploiement de la production. Cette étape est proposée de manière native sur AWS CodePipeline : il s’agit tout simplement de l’étape d’approbation manuelle, qui est bien documentée par AWS et pour laquelle nous ne reviendrons pas en détails dans cet article.
Notre seul conseil : n’ajoutez pas de notification SNS à votre approbation manuelle pour limiter le “spam”, puisque cette approbation manuelle ne le sera bientôt plus.
Pour transformer cette approbation manuelle en approbation programmée, nous allons avoir recours à AWS Lambda et CloudWatch Events. AWS CloudFormation sera également de la partie pour automatiser au maximum le déploiement de cette solution au niveau infrastructure.
Déploiement de notre petit trick
Comme nos clients, nous adorons tout automatiser chez Osones. Nous avons donc fait un template AWS CloudFormation disponible directement sur notre repo GitHub. Passons le en revue ensemble :
LambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Code:
ZipFile:
!Join
- ' '
- - 'var AWS = require("aws-sdk");'
- 'exports.handler = (event, context, callback) => {'
- ' var paramsGet = {'
- ' name: process.env.PIPELINE_NAME'
- ' };'
- ' var codepipeline = new AWS.CodePipeline();'
- ' codepipeline.getPipelineState(paramsGet, function(err, data) {'
- ' if (err) console.log(err, err.stack);'
- ' else {'
- ' if (data.stageStates.length > 0) {'
- ' var token = null;'
- ' for (let stage of data.stageStates) {'
- ' var stageName = stage.stageName;'
- ' if (stageName === process.env.STAGE_NAME) {'
- ' var actionStates = stage.actionStates;'
- ' for (let actionState of actionStates) {'
- ' var actionName = actionState.actionName;'
- ' if (actionName === process.env.APPROVAL_NAME) {'
- ' token = actionState.latestExecution.token;'
- ' break;'
- ' }'
- ' }'
- ' }'
- ' }'
- ' var paramsPut = {'
- ' actionName: process.env.APPROVAL_NAME,'
- ' pipelineName: process.env.PIPELINE_NAME,'
- ' result: {'
- ' status: "Approved",'
- ' summary: "Scheduled approval by Lambda."'
- ' },'
- ' stageName: process.env.STAGE_NAME,'
- ' token: token'
- ' };'
- ' if (token != null) {'
- ' codepipeline.putApprovalResult(paramsPut, function(err, data) {'
- ' if (err) console.log(err, err.stack);'
- ' else console.log(data);'
- ' });'
- ' }'
- ' }'
- ' }'
- ' callback(null, "Approved");'
- ' })'
- '};'
Description: Automatic CodePipeline Approval
Environment:
Variables:
PIPELINE_NAME: !Ref PipelineName
STAGE_NAME: !Ref StageName
APPROVAL_NAME: !Ref ApprovalName
FunctionName: AutomaticCodePipelineApproval
Handler: "index.handler"
Role: !GetAtt LambdaAutoApprovalServiceRole.Arn
Runtime: nodejs6.10
Avant tout nous déclarons notre code avec un Fn::Join
et un délimiteur ‘espace’ de manière à pouvoir injecter de manière lisible notre script JavaScript dans ce template, et qu’il soit exécutable sur la Lambda (le script sera sur une seule ligne dans l’éditeur en ligne).
Petite libertée : si normalement, la best practice consiste à placer ce script Javascript dans un bucket Amazon S3 puis créer la Lambda en faisant récupérer le code par CloudFormation dans ce bucket, nous nous permettons ici de le laisser directement dans le template étant donné sa taille modeste.
Nous déclarons ensuite des variables d’environnement afin de pouvoir réutiliser cette fonction AWS Lambda. A la fin, nous pouvons voir que nous lions la Lambda à un rôle. Ce rôle est assez important :
LambdaAutoApprovalServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument: |
{
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": [ "lambda.amazonaws.com" ]},
"Action": [ "sts:AssumeRole" ]
}]
}
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- arn:aws:logs:*:*:*
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Resource: "*"
Effect: Allow
Action:
- codepipeline:GetPipeline
- codepipeline:GetPipelineState
- codepipeline:GetPipelineExecution
- codepipeline:ListPipelineExecutions
- codepipeline:ListPipelines
- codepipeline:PutApprovalResult
Ce rôle consiste à donner des droits d'écriture de logs dans CloudWatch Logs et à valider les approbations dans CodePipeline. Notez que si vous avez des politiques de sécurité strictes, vous devriez restreindre les accès au minimum dans le champs Resource
.
Maintenant que la Lambda est créée, nous devons définir la “CloudWatch Event Rule” :
ScheduledRule:
Type: AWS::Events::Rule
Properties:
Description: Scheduled CodePipeline approval
ScheduleExpression: "cron(0 9 ? * MON *)"
State: ENABLED
Targets:
-
Arn: !GetAtt LambdaFunction.Arn
Id: LambdaAutomaticApproval
Cette définition est courte, il s'agit juste du planning (expression cron) et de la cible sur laquelle lier, notre Lambda en effet.
Pour rendre la règle d'événement CloudWatch capable d'invoquer le Lambda, nous devons lui donner la permission avec une déclaration AWS::Lambda::Permission
:
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt ScheduledRule.Arn
Et voilà ! Vous pouvez désormais utiliser AWS CodePipeline avec des mises en production régulières tous les lundi matins !
Alexandre KERVADEC
Découvrez les derniers articles d'alter way
- : Prowler : L'outil de sécurité multi-cloud indispensable pour renforcer votre infrastructure
- : Kubernetes : plateforme "star" de l'IT et levier d'innovation des entreprises
- AI_dev2024
- : DirectPV : Avoir du stockage bloc distribué facilement dans kubernetes
- : Simple comme GitOps : kluctl
- Conférence Wax 2024 @thecamp