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)); }
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); }
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); }
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); }
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}"; } }
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); }
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; }
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() })); }
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}/" }); }