Esempio n. 1
0
        private void ConfigureTaskDefinition(IRecipeProps <Configuration> props)
        {
            var settings = props.Settings;

            AppTaskDefinition = new FargateTaskDefinition(this, nameof(AppTaskDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppTaskDefinition), this, new FargateTaskDefinitionProps
            {
                TaskRole       = AppIAMTaskRole,
                Cpu            = settings.TaskCpu,
                MemoryLimitMiB = settings.TaskMemory
            }));

            AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps
            {
                StreamPrefix = props.StackName
            }));

            if (string.IsNullOrEmpty(props.ECRRepositoryName))
            {
                throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty.");
            }

            var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", props.ECRRepositoryName);

            AppContainerDefinition = new ContainerDefinitionOptions
            {
                Image   = ContainerImage.FromEcrRepository(ecrRepository, props.ECRImageTag),
                Logging = AppLogging
            };

            AppTaskDefinition.AddContainer(nameof(AppContainerDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppContainerDefinition), this, AppContainerDefinition));
        }
Esempio n. 2
0
 public Recipe(Construct scope, IRecipeProps <Configuration> props)
 // The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
 // change the expected values for the "DisplayedResources" in the corresponding recipe file.
     : base(scope, "Recipe")
 {
     ConfigureIAMRoles(props.Settings);
     ConfigureAppRunnerService(props);
 }
Esempio n. 3
0
 public Recipe(Construct scope, IRecipeProps <Configuration> props)
 // The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
 // change the expected values for the "DisplayedResources" in the corresponding recipe file.
     : base(scope, "Recipe")
 {
     ConfigureS3ContentBucket();
     ConfigureCloudFrontDistribution(props.Settings);
     ConfigureS3Deployment(props);
 }
Esempio n. 4
0
        public Recipe(Construct scope, IRecipeProps <Configuration> props)
        // The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
        // change the expected values for the "DisplayedResources" in the corresponding recipe file.
            : base(scope, "Recipe")
        {
            var settings = props.Settings;

            ConfigureVpc(settings);
            ConfigureApplicationIAMRole(settings);
            ConfigureECSClusterAndService(props);
            ConfigureLoadBalancer(settings);
            ConfigureAutoScaling(settings);
        }
Esempio n. 5
0
        public Recipe(Construct scope, IRecipeProps <Configuration> props)
        // The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
        // change the expected values for the "DisplayedResources" in the corresponding recipe file.
            : base(scope, "Recipe")
        {
            var settings = props.Settings;

            ConfigureIAM(settings);
            ConfigureVpc(settings);
            ConfigureTaskDefinition(props);
            ConfigureCluster(settings);
            ConfigureScheduledTask(settings);
        }
        /// <summary>
        /// Tags the stack to identify it as an AWS .NET deployment tool Clould Application. Appropriate metadata as also applied to the generated
        /// template to identify the recipe used as well as the last AWS .NET deployment tool settings. This is required to support the
        /// AWS .NET deployment tool to be able to redeploy new versions of the application to AWS.
        /// </summary>
        /// <typeparam name="C">The configuration type defined as part of the recipe that contains all of the recipe specific settings.</typeparam>
        /// <param name="stack"></param>
        /// <param name="recipeConfiguration"></param>
        public static void RegisterStack <C>(Stack stack, IRecipeProps <C> recipeConfiguration)
        {
            // Set the AWS .NET deployment tool tag which also identifies the recipe used.
            stack.Tags.SetTag(Constants.CloudFormationIdentifier.STACK_TAG, $"{recipeConfiguration.RecipeId}");

            // Serializes all AWS .NET deployment tool settings.
            var json = JsonSerializer.Serialize(
                recipeConfiguration.Settings,
                new JsonSerializerOptions
            {
                WriteIndented = false,
                Converters    = { new JsonStringEnumConverter() }
            });

            Dictionary <string, object> metadata;

            if (stack.TemplateOptions.Metadata?.Count > 0)
            {
                metadata = new Dictionary <string, object>(stack.TemplateOptions.Metadata);
            }
            else
            {
                metadata = new Dictionary <string, object>();
            }

            // Save the settings, recipe id and version as metadata to the CloudFormation template.
            metadata[Constants.CloudFormationIdentifier.STACK_METADATA_SETTINGS]       = json;
            metadata[Constants.CloudFormationIdentifier.STACK_METADATA_RECIPE_ID]      = recipeConfiguration.RecipeId;
            metadata[Constants.CloudFormationIdentifier.STACK_METADATA_RECIPE_VERSION] = recipeConfiguration.RecipeVersion;

            // For the CDK to pick up the changes to the metadata .NET Dictionary you have to reassign the Metadata property.
            stack.TemplateOptions.Metadata = metadata;

            // CloudFormation tags are propagated to resources created by the stack. In case of Beanstalk deployment a second CloudFormation stack is
            // launched which will also have the AWS .NET deployment tool tag. To differentiate these additional stacks a special AWS .NET deployment tool prefix
            // is added to the description.
            if (string.IsNullOrEmpty(stack.TemplateOptions.Description))
            {
                stack.TemplateOptions.Description = Constants.CloudFormationIdentifier.STACK_DESCRIPTION_PREFIX;
            }
            else
            {
                stack.TemplateOptions.Description = $"{Constants.CloudFormationIdentifier.STACK_DESCRIPTION_PREFIX}: {stack.TemplateOptions.Description}";
            }
        }
Esempio n. 7
0
        public Recipe(Construct scope, IRecipeProps <Configuration> props)
        // The "Recipe" construct ID will be used as part of the CloudFormation logical ID. If the value is changed this will
        // change the expected values for the "DisplayedResources" in the corresponding recipe file.
            : base(scope, "Recipe")
        {
            var settings = props.Settings;

            if (string.IsNullOrEmpty(props.DotnetPublishZipPath))
            {
                throw new InvalidOrMissingConfigurationException("The provided path containing the dotnet publish zip file is null or empty.");
            }

            ApplicationAsset = new Asset(this, "Asset", new AssetProps
            {
                Path = props.DotnetPublishZipPath
            });

            ConfigureIAM(settings);
            ConfigureApplication(settings);
            ConfigureBeanstalkEnvironment(settings);
        }
Esempio n. 8
0
        private void ConfigureS3Deployment(IRecipeProps <Configuration> props)
        {
            if (ContentS3Bucket == null)
            {
                throw new InvalidOperationException($"{nameof(ContentS3Bucket)} has not been set. The {nameof(ConfigureS3ContentBucket)} method should be called before {nameof(ContentS3Bucket)}");
            }

            if (string.IsNullOrEmpty(props.DotnetPublishOutputDirectory))
            {
                throw new InvalidOrMissingConfigurationException("The provided path containing the dotnet publish output is null or empty.");
            }

            var bucketDeploymentProps = new BucketDeploymentProps
            {
                Sources           = new ISource[] { Source.Asset(Path.Combine(props.DotnetPublishOutputDirectory, "wwwroot")) },
                DestinationBucket = ContentS3Bucket,
                MemoryLimit       = 4096,

                Distribution      = CloudFrontDistribution,
                DistributionPaths = new string[] { "/*" }
            };

            ContentS3Deployment = new BucketDeployment(this, nameof(ContentS3Deployment), InvokeCustomizeCDKPropsEvent(nameof(ContentS3Deployment), this, bucketDeploymentProps));
        }
 public DeployToolStackProps(IRecipeProps <T> props)
 {
     RecipeProps = props;
     StackName   = props.StackName;
 }
Esempio n. 10
0
        private void ConfigureECSClusterAndService(IRecipeProps <Configuration> recipeConfiguration)
        {
            if (AppVpc == null)
            {
                throw new InvalidOperationException($"{nameof(AppVpc)} has not been set. The {nameof(ConfigureVpc)} method should be called before {nameof(ConfigureECSClusterAndService)}");
            }

            var settings = recipeConfiguration.Settings;

            if (settings.ECSCluster.CreateNew)
            {
                EcsCluster = new Cluster(this, nameof(EcsCluster), InvokeCustomizeCDKPropsEvent(nameof(EcsCluster), this, new ClusterProps
                {
                    Vpc         = AppVpc,
                    ClusterName = settings.ECSCluster.NewClusterName
                }));
            }
            else
            {
                EcsCluster = Cluster.FromClusterAttributes(this, nameof(EcsCluster), InvokeCustomizeCDKPropsEvent(nameof(EcsCluster), this, new ClusterAttributes
                {
                    ClusterArn     = settings.ECSCluster.ClusterArn,
                    ClusterName    = ECSFargateUtilities.GetClusterNameFromArn(settings.ECSCluster.ClusterArn),
                    SecurityGroups = new ISecurityGroup[0],
                    Vpc            = AppVpc
                }));
            }

            AppTaskDefinition = new FargateTaskDefinition(this, nameof(AppTaskDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppTaskDefinition), this, new FargateTaskDefinitionProps
            {
                TaskRole       = AppIAMTaskRole,
                Cpu            = settings.TaskCpu,
                MemoryLimitMiB = settings.TaskMemory
            }));

            AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps
            {
                StreamPrefix = recipeConfiguration.StackName
            }));

            if (string.IsNullOrEmpty(recipeConfiguration.ECRRepositoryName))
            {
                throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty.");
            }

            EcrRepository          = Repository.FromRepositoryName(this, nameof(EcrRepository), recipeConfiguration.ECRRepositoryName);
            AppContainerDefinition = AppTaskDefinition.AddContainer(nameof(AppContainerDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppContainerDefinition), this, new ContainerDefinitionOptions
            {
                Image   = ContainerImage.FromEcrRepository(EcrRepository, recipeConfiguration.ECRImageTag),
                Logging = AppLogging
            }));

            AppContainerDefinition.AddPortMappings(new PortMapping
            {
                ContainerPort = 80,
                Protocol      = Amazon.CDK.AWS.ECS.Protocol.TCP
            });

            WebAccessSecurityGroup = new SecurityGroup(this, nameof(WebAccessSecurityGroup), InvokeCustomizeCDKPropsEvent(nameof(WebAccessSecurityGroup), this, new SecurityGroupProps
            {
                Vpc = AppVpc,
                SecurityGroupName = $"{recipeConfiguration.StackName}-ECSService"
            }));

            EcsServiceSecurityGroups = new List <ISecurityGroup>();
            EcsServiceSecurityGroups.Add(WebAccessSecurityGroup);

            if (!string.IsNullOrEmpty(settings.AdditionalECSServiceSecurityGroups))
            {
                var count = 1;
                foreach (var securityGroupId in settings.AdditionalECSServiceSecurityGroups.Split(','))
                {
                    EcsServiceSecurityGroups.Add(SecurityGroup.FromSecurityGroupId(this, $"AdditionalGroup-{count++}", securityGroupId.Trim(), new SecurityGroupImportOptions
                    {
                        Mutable = false
                    }));
                }
            }

            AppFargateService = new FargateService(this, nameof(AppFargateService), InvokeCustomizeCDKPropsEvent(nameof(AppFargateService), this, new FargateServiceProps
            {
                Cluster        = EcsCluster,
                TaskDefinition = AppTaskDefinition,
                DesiredCount   = settings.DesiredCount,
                ServiceName    = settings.ECSServiceName,
                AssignPublicIp = settings.Vpc.IsDefault,
                SecurityGroups = EcsServiceSecurityGroups.ToArray()
            }));
        }
Esempio n. 11
0
        private void ConfigureAppRunnerService(IRecipeProps <Configuration> props)
        {
            if (ServiceAccessRole == null)
            {
                throw new InvalidOperationException($"{nameof(ServiceAccessRole)} has not been set. The {nameof(ConfigureIAMRoles)} method should be called before {nameof(ConfigureAppRunnerService)}");
            }
            if (TaskRole == null)
            {
                throw new InvalidOperationException($"{nameof(TaskRole)} has not been set. The {nameof(ConfigureIAMRoles)} method should be called before {nameof(ConfigureAppRunnerService)}");
            }

            if (string.IsNullOrEmpty(props.ECRRepositoryName))
            {
                throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty.");
            }

            var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", props.ECRRepositoryName);

            Configuration settings             = props.Settings;
            var           appRunnerServiceProp = new CfnServiceProps
            {
                ServiceName         = settings.ServiceName,
                SourceConfiguration = new CfnService.SourceConfigurationProperty
                {
                    AuthenticationConfiguration = new CfnService.AuthenticationConfigurationProperty
                    {
                        AccessRoleArn = ServiceAccessRole.RoleArn
                    },
                    ImageRepository = new CfnService.ImageRepositoryProperty
                    {
                        ImageRepositoryType = "ECR",
                        ImageIdentifier     = ContainerImage.FromEcrRepository(ecrRepository, props.ECRImageTag).ImageName,
                        ImageConfiguration  = new CfnService.ImageConfigurationProperty
                        {
                            Port         = settings.Port.ToString(),
                            StartCommand = !string.IsNullOrWhiteSpace(settings.StartCommand) ? settings.StartCommand : null
                        }
                    }
                }
            };

            if (!string.IsNullOrEmpty(settings.EncryptionKmsKey))
            {
                var encryptionConfig = new CfnService.EncryptionConfigurationProperty();
                appRunnerServiceProp.EncryptionConfiguration = encryptionConfig;

                encryptionConfig.KmsKey = settings.EncryptionKmsKey;
            }

            var healthCheckConfig = new CfnService.HealthCheckConfigurationProperty();

            appRunnerServiceProp.HealthCheckConfiguration = healthCheckConfig;

            healthCheckConfig.HealthyThreshold   = settings.HealthCheckHealthyThreshold;
            healthCheckConfig.Interval           = settings.HealthCheckInterval;
            healthCheckConfig.Protocol           = settings.HealthCheckProtocol;
            healthCheckConfig.Timeout            = settings.HealthCheckTimeout;
            healthCheckConfig.UnhealthyThreshold = settings.HealthCheckUnhealthyThreshold;

            if (string.Equals(healthCheckConfig.Protocol, "HTTP"))
            {
                healthCheckConfig.Path = string.IsNullOrEmpty(settings.HealthCheckPath) ? "/" : settings.HealthCheckPath;
            }

            var instanceConfig = new CfnService.InstanceConfigurationProperty();

            appRunnerServiceProp.InstanceConfiguration = instanceConfig;


            instanceConfig.InstanceRoleArn = TaskRole.RoleArn;

            instanceConfig.Cpu    = settings.Cpu;
            instanceConfig.Memory = settings.Memory;

            AppRunnerService = new CfnService(this, nameof(AppRunnerService), InvokeCustomizeCDKPropsEvent(nameof(AppRunnerService), this, appRunnerServiceProp));

            var output = new CfnOutput(this, "EndpointURL", new CfnOutputProps
            {
                Value = $"https://{AppRunnerService.AttrServiceUrl}/"
            });
        }