File: Provisioning\CloudFormationTemplateResourceProvisioner.cs
Web Access
Project: src\src\Aspire.Hosting.AWS\Aspire.Hosting.AWS.csproj (Aspire.Hosting.AWS)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.AWS.CloudFormation;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Hosting.AWS.Provisioning;
 
internal class CloudFormationTemplateResourceProvisioner(
    ResourceLoggerService loggerService,
    ResourceNotificationService notificationService)
    : CloudFormationTemplateResourceProvisioner<CloudFormationTemplateResource>(loggerService, notificationService);
 
internal class CloudFormationTemplateResourceProvisioner<T>(
    ResourceLoggerService loggerService,
    ResourceNotificationService notificationService)
    : CloudFormationResourceProvisioner<T>(loggerService, notificationService)
    where T : CloudFormationTemplateResource
{
    protected override async Task GetOrCreateResourceAsync(T resource,
        CancellationToken cancellationToken)
    {
        var logger = LoggerService.GetLogger(resource);
 
        using var cfClient = GetCloudFormationClient(resource);
 
        try
        {
            var context = await CreateCloudFormationExecutionContextAsync(resource, cancellationToken)
                .ConfigureAwait(false);
            var executor = new CloudFormationStackExecutor(cfClient, context, logger);
            var stack = await executor.ExecuteTemplateAsync(cancellationToken).ConfigureAwait(false);
 
            if (stack != null)
            {
                logger.LogInformation("CloudFormation stack has {Count} output parameters", stack.Outputs.Count);
                if (logger.IsEnabled(LogLevel.Information))
                {
                    foreach (var output in stack.Outputs)
                    {
                        logger.LogInformation("Output Name: {Name}, Value {Value}", output.OutputKey, output.OutputValue);
                    }
                }
 
                logger.LogInformation("CloudFormation provisioning complete");
 
                if (resource is CloudFormationResource cloudformationResource)
                {
                    cloudformationResource.Outputs = stack.Outputs;
                }
 
                var templatePath = resource.TemplatePath;
                await PublishCloudFormationUpdatePropertiesAsync(resource, ConvertOutputToProperties(stack, templatePath),
                    MapCloudFormationStackUrl(cfClient, stack.StackId)).ConfigureAwait(false);
            }
            else
            {
                logger.LogError("CloudFormation provisioning failed");
 
                throw new AWSProvisioningException("Failed to apply CloudFormation template");
            }
        }
        catch (Exception ex)
        {
            HandleTemplateProvisioningException(ex, resource, logger);
            throw;
        }
    }
 
    private static async Task<CloudFormationStackExecutionContext> CreateCloudFormationExecutionContextAsync(T resource, CancellationToken cancellationToken)
    {
        var template = await File.ReadAllTextAsync(resource.TemplatePath, cancellationToken).ConfigureAwait(false);
        return new CloudFormationStackExecutionContext(resource.StackName, template)
        {
            RoleArn = resource.RoleArn,
            DisableDiffCheck = resource.DisableDiffCheck,
            StackPollingInterval = resource.StackPollingInterval,
            DisabledCapabilities = resource.DisabledCapabilities,
            CloudFormationParameters = resource.CloudFormationParameters
        };
    }
 
    protected virtual void HandleTemplateProvisioningException(Exception ex, T resource, ILogger logger) { }
}