// This sample, non-production-ready project that demonstrates how to detect when an Amazon Elastic Beanstalk // platform's base AMI has been updated and starts an EC2 Image Builder Pipeline to automate the creation of a golden image. // © 2021 Amazon Web Services, Inc. or its affiliates. All Rights Reserved. // This AWS Content is provided subject to the terms of the AWS Customer Agreement available at // http://aws.amazon.com/agreement or other written agreement between Customer and either // Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both. // SPDX-License-Identifier: MIT-0 namespace BeanstalkImageBuilderPipeline { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Amazon.Imagebuilder; using Amazon.Imagebuilder.Model; using Amazon.Lambda.CloudWatchEvents; using Amazon.Lambda.Core; using Amazon.SimpleSystemsManagement; using BeanstalkImageBuilderPipeline.Events; using BeanstalkImageBuilderPipeline.Repositories; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public sealed class ImageBuilderTrigger : LambdaFunction { /// /// Constructor used by Lambda at runtime. /// [ExcludeFromCodeCoverage] public ImageBuilderTrigger() { } public ImageBuilderTrigger(IServiceProvider serviceProvider) : base(serviceProvider) { } [ExcludeFromCodeCoverage] protected override void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services) { services.AddScoped(); services.AddScoped(); services.AddAWSService(); services.AddAWSService(); } public async Task Handler(CloudWatchEvent cloudWatchEvent, ILambdaContext context) { var logger = ServiceProvider.GetRequiredService>(); using (logger.BeginScope(new Dictionary {["AwsRequestId"] = context.AwsRequestId})) { try { if (!string.IsNullOrEmpty(cloudWatchEvent.Detail.Exception)) { logger.LogInformation("Event was for an exception during SSM Parameter operation {ParameterName}. Skipping Image Builder version bump.", cloudWatchEvent.Detail.Name); return; } if (!string.Equals(cloudWatchEvent.Detail.Operation, "update", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(cloudWatchEvent.Detail.Operation, "create", StringComparison.InvariantCultureIgnoreCase)) { logger.LogInformation("Event was {EventOperation} and not for an update/create of SSM Parameter operation {ParameterName}. Skipping Image Builder version bump.", cloudWatchEvent.Detail.Operation, cloudWatchEvent.Detail.Name); return; } var ssmRepository = ServiceProvider.GetRequiredService(); string newAmiId = await ssmRepository.GetParameterValueAsync(cloudWatchEvent.Detail.Name); if (string.IsNullOrEmpty(newAmiId)) { logger.LogWarning("Could not find SSM Parameter named {ParameterName}. Skipping Image Builder version bump.", cloudWatchEvent.Detail.Name); return; } var imagePipelineArn = Environment.GetEnvironmentVariable("IMAGE_PIPELINE_ARN"); var imageBuilderRepository = ServiceProvider.GetRequiredService(); await UpdateImagePipelineForNewAmiAsync(imagePipelineArn, newAmiId, imageBuilderRepository, logger); logger.LogInformation("Starting image pipeline execution for {ImagePipelineArn}.", imagePipelineArn); await imageBuilderRepository.StartImagePipelineExecutionAsync(imagePipelineArn); } catch (Exception ex) { logger.LogError(ex, "Unhandled Exception During Lambda Invocation"); throw; } } } private async Task UpdateImagePipelineForNewAmiAsync(string imagePipelineArn, string newAmiId, IImageBuilderRepository imageBuilderRepository, ILogger logger) { ImagePipeline imagePipeline = await imageBuilderRepository.GetPipelineByIdAsync(imagePipelineArn); ImageRecipe imageRecipe = await imageBuilderRepository.GetRecipeByIdAsync(imagePipeline.ImageRecipeArn); string currentVersion = imageRecipe.Version; Version version = Version.Parse(currentVersion); imageRecipe.Version = $"{version.Major}.{version.Minor}.{version.Build + 1}"; imageRecipe.ParentImage = newAmiId; logger.LogInformation("Incrementing Recipe {RecipeArn} from {CurrentVersion} to {NewVersion}, using AMI {NewAmiId}", imageRecipe.Arn, currentVersion, imageRecipe.Version, newAmiId); var newImageRecipe = await imageBuilderRepository.CreateImageRecipeAsync(imageRecipe); imagePipeline.ImageRecipeArn = newImageRecipe.Arn; logger.LogInformation("Updating Image Pipeline to use Recipe {NewRecipeArn}", newImageRecipe.Arn); await imageBuilderRepository.UpdateImagePipelineAsync(imagePipeline); } } }