Ejemplo n.º 1
0
        public Vec2 GetOffset(ModuleLocation modloc)
        {
            switch (modloc)
            {
            case ModuleLocation.Sight:
                return(new Vec2(5, -5));

            case ModuleLocation.Barrel:
                return(DefBarrOffTl - _center + _extraOffset + new Vec2(-2f, -0.5f));

            default:
                return(new Vec2());
            }
        }
Ejemplo n.º 2
0
        private async Task <ModuleLocation> FindExistingDependencyAsync(ModuleManifestDependency dependency)
        {
            try {
                var describe = await Settings.CfnClient.DescribeStacksAsync(new DescribeStacksRequest {
                    StackName = ToStackName(dependency.ModuleFullName)
                });

                var deployedOutputs = describe.Stacks.FirstOrDefault()?.Outputs;
                var deployedInfo    = deployedOutputs?.FirstOrDefault(output => output.OutputKey == "Module")?.OutputValue;
                var success         = deployedInfo.TryParseModuleDescriptor(
                    out string deployedOwner,
                    out string deployedName,
                    out VersionInfo deployedVersion,
                    out string deployedBucketName
                    );
                var deployed = new ModuleLocation(deployedOwner, deployedName, deployedVersion, deployedBucketName);
                if (!success)
                {
                    LogError($"unable to retrieve information of the deployed dependent module");
                    return(deployed);
                }

                // confirm that the module name matches
                if (deployed.ModuleFullName != dependency.ModuleFullName)
                {
                    LogError($"deployed dependent module name ({deployed.ModuleFullName}) does not match {dependency.ModuleFullName}");
                    return(deployed);
                }

                // confirm that the module version is in a valid range
                if ((dependency.MaxVersion != null) && (deployedVersion > dependency.MaxVersion))
                {
                    LogError($"deployed dependent module version (v{deployedVersion}) is newer than max version constraint v{dependency.MaxVersion}");
                    return(deployed);
                }
                if ((dependency.MinVersion != null) && (deployedVersion < dependency.MinVersion))
                {
                    LogError($"deployed dependent module version (v{deployedVersion}) is older than min version constraint v{dependency.MinVersion}");
                    return(deployed);
                }
                return(deployed);
            } catch (AmazonCloudFormationException) {
                // stack doesn't exist
                return(null);
            }
        }
Ejemplo n.º 3
0
        //--- Methods ---
        public async Task <bool> DoAsync(
            DryRunLevel?dryRun,
            string moduleReference,
            string instanceName,
            bool allowDataLoos,
            bool protectStack,
            Dictionary <string, string> parameters,
            bool forceDeploy,
            bool promptAllParameters,
            XRayTracingLevel xRayTracingLevel,
            bool deployOnlyIfExists,
            bool allowDependencyUpgrades
            )
        {
            Console.WriteLine($"Resolving module reference: {moduleReference}");

            // determine location of cloudformation template from module key
            if (!ModuleInfo.TryParse(moduleReference, out var moduleInfo))
            {
                LogError($"invalid module reference: {moduleReference}");
                return(false);
            }
            var foundModuleLocation = await _loader.ResolveInfoToLocationAsync(moduleInfo, moduleInfo.Origin, ModuleManifestDependencyType.Root, allowImport : Settings.AllowImport, showError : !deployOnlyIfExists);

            if (foundModuleLocation == null)
            {
                // nothing to do; loader already emitted an error
                return(deployOnlyIfExists);
            }

            // download module manifest
            var(manifest, manifestErrorReason) = await _loader.LoadManifestFromLocationAsync(foundModuleLocation);

            if (manifest == null)
            {
                LogError(manifestErrorReason);
                return(false);
            }

            // deploy module
            if (dryRun == null)
            {
                var stackName = Settings.GetStackName(manifest.GetFullName(), instanceName);

                // check version of previously deployed module
                if (!deployOnlyIfExists)
                {
                    Console.WriteLine("=> Validating module for deployment tier");
                }
                var updateValidation = await IsValidModuleUpdateAsync(stackName, manifest, showError : !forceDeploy && !deployOnlyIfExists);

                if (!forceDeploy && !updateValidation.Success)
                {
                    return(false);
                }

                // check if a previous deployment was found
                if (deployOnlyIfExists && (updateValidation.ExistingStack == null))
                {
                    // nothing to do
                    return(true);
                }
                var existing = updateValidation.ExistingStack;

                // check if existing stack checksum matches template checksum
                if (!forceDeploy && !parameters.Any())
                {
                    var existingChecksum = existing?.Outputs.FirstOrDefault(output => output.OutputKey == "ModuleChecksum");
                    if (existingChecksum?.OutputValue == manifest.TemplateChecksum)
                    {
                        Console.WriteLine($"{Settings.LowContrastColor}=> No changes found to deploy{Settings.ResetColor}");
                        return(true);
                    }
                }

                // prompt for missing parameters
                var deployParameters = PromptModuleParameters(manifest, existing, parameters, promptAllParameters);
                if (HasErrors)
                {
                    return(false);
                }

                // check if module supports AWS X-Ray for tracing
                if (
                    manifest.GetAllParameters().Any(p => p.Name == "XRayTracing") &&
                    !deployParameters.Any(p => p.ParameterKey == "XRayTracing")
                    )
                {
                    deployParameters.Add(new CloudFormationParameter {
                        ParameterKey   = "XRayTracing",
                        ParameterValue = xRayTracingLevel.ToString()
                    });
                }

                // discover shared module dependencies and prompt for missing parameters
                var dependencies = (await _loader.DiscoverAllDependenciesAsync(manifest, checkExisting: true, allowImport: Settings.AllowImport, allowDependencyUpgrades: allowDependencyUpgrades))
                                   .Where(dependency => dependency.Type == ModuleManifestDependencyType.Shared)
                                   .ToList();
                if (HasErrors)
                {
                    return(false);
                }
                var dependenciesParameters = dependencies
                                             .Select(dependency => new {
                    ModuleFullName = dependency.Manifest.GetFullName(),
                    Parameters     = PromptModuleParameters(
                        dependency.Manifest,
                        promptAll: promptAllParameters
                        )
                })
                                             .ToDictionary(t => t.ModuleFullName, t => t.Parameters);
                if (HasErrors)
                {
                    return(false);
                }

                // deploy module dependencies
                foreach (var dependency in dependencies)
                {
                    var dependencyLocation = new ModuleLocation(Settings.DeploymentBucketName, dependency.ModuleLocation.ModuleInfo, dependency.ModuleLocation.Hash);
                    if (!await new ModelUpdater(Settings, SourceFilename).DeployChangeSetAsync(
                            dependency.Manifest,
                            await _loader.GetNameMappingsFromLocationAsync(dependencyLocation),
                            dependencyLocation,
                            Settings.GetStackName(dependency.Manifest.GetFullName()),
                            allowDataLoos,
                            protectStack,
                            dependenciesParameters[dependency.Manifest.GetFullName()]
                            ))
                    {
                        return(false);
                    }
                }

                // deploy module
                var moduleLocation = new ModuleLocation(Settings.DeploymentBucketName, manifest.ModuleInfo, manifest.TemplateChecksum);
                return(await new ModelUpdater(Settings, moduleReference).DeployChangeSetAsync(
                           manifest,
                           await _loader.GetNameMappingsFromLocationAsync(moduleLocation),
                           moduleLocation,
                           stackName,
                           allowDataLoos,
                           protectStack,
                           deployParameters
                           ));
            }
            return(true);
        }
Ejemplo n.º 4
0
        //--- Methods ---
        public async Task <bool> DeployChangeSetAsync(
            ModuleManifest manifest,
            ModuleNameMappings nameMappings,
            ModuleLocation moduleLocation,
            string stackName,
            bool allowDataLoss,
            bool protectStack,
            List <CloudFormationParameter> parameters
            )
        {
            var now = DateTime.UtcNow;

            // check if cloudformation stack already exists and is in a final state
            Console.WriteLine();
            Console.WriteLine($"Deploying stack: {stackName} [{moduleLocation.ModuleInfo}]");
            var mostRecentStackEventId = await Settings.CfnClient.GetMostRecentStackEventIdAsync(stackName);

            // validate template (must have been copied to deployment bucket at this stage)
            if (moduleLocation.SourceBucketName != Settings.DeploymentBucketName)
            {
                LogError($"module source must match the deployment tier S3 bucket (EXPECTED: {Settings.DeploymentBucketName}, FOUND: {moduleLocation.SourceBucketName})");
                return(false);
            }
            ValidateTemplateResponse validation;

            try {
                validation = await Settings.CfnClient.ValidateTemplateAsync(new ValidateTemplateRequest {
                    TemplateURL = moduleLocation.ModuleTemplateUrl
                });
            } catch (AmazonCloudFormationException e) {
                LogError($"{e.Message} (url: {moduleLocation.ModuleTemplateUrl})");
                return(false);
            }

            // verify if we need to remove the old CloudFormation notification ARNs that were created by LambdaSharp config before v0.7
            List <string>      notificationARNs = null;
            ModuleNameMappings oldNameMappings  = null;

            if (mostRecentStackEventId != null)
            {
                // fetch name mappings for current template; this is needed to properly map logical IDs to their original names when they get deleted
                oldNameMappings = await new ModelManifestLoader(Settings, "source").GetNameMappingsFromCloudFormationStackAsync(stackName);

                // NOTE (2019-09-19, bjorg): this is a HACK to remove the old notification ARNs, because doing it part
                //  of the change set is not working for some reason.
                var deployedModule = await Settings.CfnClient.GetStackAsync(stackName, LogError);

                notificationARNs = deployedModule.Stack.NotificationARNs.Where(arn => !arn.Contains(":LambdaSharpTool-")).ToList();
                if (notificationARNs.Count != deployedModule.Stack.NotificationARNs.Count)
                {
                    Console.WriteLine("=> Removing legacy stack notification ARN");
                    var update = await Settings.CfnClient.UpdateStackAsync(new UpdateStackRequest {
                        StackName           = stackName,
                        UsePreviousTemplate = true,
                        Parameters          = deployedModule.Stack.Parameters.Select(param => new CloudFormationParameter {
                            ParameterKey     = param.ParameterKey,
                            UsePreviousValue = true
                        }).ToList(),
                        NotificationARNs = notificationARNs,
                        Capabilities     = validation.Capabilities
                    });

                    var outcome = await Settings.CfnClient.TrackStackUpdateAsync(stackName, update.StackId, mostRecentStackEventId, nameMappings, oldNameMappings, LogError);

                    if (!outcome.Success)
                    {
                        LogError("failed to remove legacy stack notification ARN; remove manually and try again");
                        return(false);
                    }
                    mostRecentStackEventId = await Settings.CfnClient.GetMostRecentStackEventIdAsync(stackName);

                    Console.WriteLine("=> Legacy stack notification ARN has been removed");
                }
            }

            // create change-set
            var success        = false;
            var changeSetName  = $"{moduleLocation.ModuleInfo.FullName.Replace(".", "-")}-{now:yyyy-MM-dd-hh-mm-ss}";
            var updateOrCreate = (mostRecentStackEventId != null) ? "update" : "create";
            var capabilities   = validation.Capabilities.Any()
                ? "[" + string.Join(", ", validation.Capabilities) + "]"
                : "";

            Console.WriteLine($"=> Stack {updateOrCreate} initiated for {Settings.InfoColor}{stackName}{Settings.ResetColor} {capabilities}");
            CreateChangeSetResponse response;

            try {
                response = await Settings.CfnClient.CreateChangeSetAsync(new CreateChangeSetRequest {
                    Capabilities  = validation.Capabilities,
                    ChangeSetName = changeSetName,
                    ChangeSetType = (mostRecentStackEventId != null) ? ChangeSetType.UPDATE : ChangeSetType.CREATE,
                    Description   = $"Stack {updateOrCreate} {moduleLocation.ModuleInfo.FullName} (v{moduleLocation.ModuleInfo.Version})",
                    Parameters    = new List <CloudFormationParameter>(parameters)
                    {
                        new CloudFormationParameter {
                            ParameterKey   = "DeploymentPrefix",
                            ParameterValue = Settings.TierPrefix
                        },
                        new CloudFormationParameter {
                            ParameterKey   = "DeploymentPrefixLowercase",
                            ParameterValue = Settings.TierPrefix.ToLowerInvariant()
                        },
                        new CloudFormationParameter {
                            ParameterKey   = "DeploymentBucketName",
                            ParameterValue = Settings.DeploymentBucketName
                        },
                        new CloudFormationParameter {
                            ParameterKey   = "DeploymentChecksum",
                            ParameterValue = manifest.TemplateChecksum
                        }
                    },
                    StackName   = stackName,
                    TemplateURL = moduleLocation.ModuleTemplateUrl,
                    Tags        = Settings.GetCloudFormationStackTags(moduleLocation.ModuleInfo.FullName, stackName)
                });
            } catch (AmazonCloudFormationException e) {
                LogError($"cloudformation change-set failed: {e.Message}");
                return(false);
            }
            try {
                var changes = await WaitForChangeSetAsync(response.Id);

                if (changes == null)
                {
                    return(false);
                }
                if (!changes.Any())
                {
                    Console.WriteLine("=> No stack update required");
                    return(true);
                }

                //  changes
                if (!allowDataLoss)
                {
                    var lossyChanges = DetectLossyChanges(changes);
                    if (lossyChanges.Any())
                    {
                        Console.WriteLine();
                        Console.WriteLine($"{Settings.AlertColor}CAUTION:{Settings.ResetColor} detected potential replacement and data-loss in the following resources");
                        var maxResourceTypeWidth = lossyChanges.Select(change => change.ResourceChange.ResourceType.Length).Max();
                        foreach (var lossy in lossyChanges)
                        {
                            if (Settings.UseAnsiConsole)
                            {
                                if (lossy.ResourceChange.Replacement == Replacement.True)
                                {
                                    Console.Write(AnsiTerminal.Red);
                                    Console.Write("ALWAYS         ");
                                }
                                else
                                {
                                    Console.Write(AnsiTerminal.Yellow);
                                    Console.Write("CONDITIONAL    ");
                                }
                                Console.Write(AnsiTerminal.Reset);
                            }
                            else
                            {
                                Console.WriteLine((lossy.ResourceChange.Replacement == Replacement.True)
                                    ? "ALWAYS         "
                                    : "CONDITIONAL    "
                                                  );
                            }
                            Console.Write(lossy.ResourceChange.ResourceType);
                            Console.Write("".PadRight(maxResourceTypeWidth - lossy.ResourceChange.ResourceType.Length + 4));
                            Console.Write(TranslateLogicalIdToFullName(lossy.ResourceChange.LogicalResourceId));
                            Console.WriteLine();
                        }
                        if (!Settings.PromptYesNo("Proceed with potentially replacing/deleting resources?", false))
                        {
                            return(false);
                        }
                        Console.WriteLine();
                    }
                }

                // execute change-set
                await Settings.CfnClient.ExecuteChangeSetAsync(new ExecuteChangeSetRequest {
                    ChangeSetName = changeSetName,
                    StackName     = stackName
                });

                var outcome = await Settings.CfnClient.TrackStackUpdateAsync(
                    stackName,
                    response.StackId,
                    mostRecentStackEventId,
                    nameMappings,
                    oldNameMappings,
                    LogError
                    );

                if (outcome.Success)
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} finished");
                    ShowStackResult(outcome.Stack);
                    success = true;
                }
                else
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} FAILED");
                }

                // optionally enable stack protection
                if (success)
                {
                    // on success, protect the stack if requested
                    if (protectStack)
                    {
                        await Settings.CfnClient.UpdateTerminationProtectionAsync(new UpdateTerminationProtectionRequest {
                            EnableTerminationProtection = protectStack,
                            StackName = stackName
                        });
                    }
                }
                else if (mostRecentStackEventId == null)
                {
                    // delete a new stack that failed to create
                    try {
                        await Settings.CfnClient.DeleteStackAsync(new DeleteStackRequest {
                            StackName = stackName
                        });
                    } catch { }
                }
                return(success);
            } finally {
                try {
                    await Settings.CfnClient.DeleteChangeSetAsync(new DeleteChangeSetRequest {
                        ChangeSetName = response.Id
                    });
                } catch { }
            }

            // local function
            string TranslateLogicalIdToFullName(string logicalId)
            {
                var fullName = logicalId;

                nameMappings?.ResourceNameMappings.TryGetValue(logicalId, out fullName);
                return(fullName ?? logicalId);
            }
        }
Ejemplo n.º 5
0
        //--- Methods ---
        public async Task <bool> DeployChangeSetAsync(
            ModuleManifest manifest,
            ModuleLocation location,
            string stackName,
            bool allowDataLoss,
            bool protectStack,
            List <CloudFormationParameter> parameters
            )
        {
            var now = DateTime.UtcNow;

            // check if cloudformation stack already exists and is in a final state
            Console.WriteLine();
            Console.WriteLine($"Deploying stack: {stackName} [{location.ModuleFullName}:{location.ModuleVersion}]");
            var mostRecentStackEventId = await Settings.CfnClient.GetMostRecentStackEventIdAsync(stackName);

            // set optional notification topics for cloudformation operations
            var notificationArns = new List <string>();

            if (Settings.DeploymentNotificationsTopic != null)
            {
                notificationArns.Add(Settings.DeploymentNotificationsTopic);
            }

            // validate template
            var templateUrl = $"https://{location.ModuleBucketName}.s3.amazonaws.com/{location.TemplatePath}";
            ValidateTemplateResponse validation;

            try {
                validation = await Settings.CfnClient.ValidateTemplateAsync(new ValidateTemplateRequest {
                    TemplateURL = templateUrl
                });
            } catch (AmazonCloudFormationException e) {
                LogError(e.Message);
                return(false);
            }

            // create change-set
            var success        = false;
            var changeSetName  = $"{location.ModuleFullName.Replace(".", "-")}-{now:yyyy-MM-dd-hh-mm-ss}";
            var updateOrCreate = (mostRecentStackEventId != null) ? "update" : "create";
            var capabilities   = validation.Capabilities.Any()
                ? "[" + string.Join(", ", validation.Capabilities) + "]"
                : "";

            Console.WriteLine($"=> Stack {updateOrCreate} initiated for {stackName} {capabilities}");
            var response = await Settings.CfnClient.CreateChangeSetAsync(new CreateChangeSetRequest {
                Capabilities     = validation.Capabilities,
                ChangeSetName    = changeSetName,
                ChangeSetType    = (mostRecentStackEventId != null) ? ChangeSetType.UPDATE : ChangeSetType.CREATE,
                Description      = $"Stack {updateOrCreate} {location.ModuleFullName} (v{location.ModuleVersion})",
                NotificationARNs = notificationArns,
                Parameters       = new List <CloudFormationParameter>(parameters)
                {
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentPrefix",
                        ParameterValue = string.IsNullOrEmpty(Settings.Tier) ? "" : (Settings.Tier + "-")
                    },
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentPrefixLowercase",
                        ParameterValue = string.IsNullOrEmpty(Settings.Tier) ? "" : (Settings.Tier.ToLowerInvariant() + "-")
                    },
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentBucketName",
                        ParameterValue = location.ModuleBucketName ?? ""
                    }
                },
                StackName   = stackName,
                TemplateURL = templateUrl,
                Tags        = new List <Tag> {
                    new Tag {
                        Key   = "LambdaSharp:Tier",
                        Value = Settings.Tier
                    },
                    new Tag {
                        Key   = "LambdaSharp:Module",
                        Value = location.ModuleFullName
                    },
                    new Tag {
                        Key   = "LambdaSharp:RootStack",
                        Value = stackName
                    },
                    new Tag {
                        Key   = "LambdaSharp:DeployedBy",
                        Value = Settings.AwsUserArn.Split(':').Last()
                    }
                }
            });

            try {
                var changes = await WaitForChangeSetAsync(response.Id);

                if (changes == null)
                {
                    return(false);
                }
                if (!changes.Any())
                {
                    Console.WriteLine("=> No stack update required");
                    return(true);
                }

                //  changes
                if (!allowDataLoss)
                {
                    var lossyChanges = DetectLossyChanges(changes);
                    if (lossyChanges.Any())
                    {
                        LogError("one or more resources could be replaced or deleted; use --allow-data-loss to proceed");
                        Console.WriteLine("=> WARNING: detected potential replacement and data-loss in the following resources");
                        foreach (var lossy in lossyChanges)
                        {
                            Console.WriteLine($"{(lossy.ResourceChange.Replacement == Replacement.True ? "ALWAYS" : "CONDITIONAL"),-12} {lossy.ResourceChange.ResourceType,-55} {TranslateLogicalIdToFullName(lossy.ResourceChange.LogicalResourceId)}");
                        }
                        return(false);
                    }
                }

                // execute change-set
                await Settings.CfnClient.ExecuteChangeSetAsync(new ExecuteChangeSetRequest {
                    ChangeSetName = changeSetName,
                    StackName     = stackName
                });

                var outcome = await Settings.CfnClient.TrackStackUpdateAsync(stackName, mostRecentStackEventId, manifest.ResourceNameMappings, manifest.TypeNameMappings, logError : LogError);

                if (outcome.Success)
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} finished");
                    ShowStackResult(outcome.Stack);
                    success = true;
                }
                else
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} FAILED");
                }

                // optionally enable stack protection
                if (success)
                {
                    // on success, protect the stack if requested
                    if (protectStack)
                    {
                        await Settings.CfnClient.UpdateTerminationProtectionAsync(new UpdateTerminationProtectionRequest {
                            EnableTerminationProtection = protectStack,
                            StackName = stackName
                        });
                    }
                }
                else if (mostRecentStackEventId == null)
                {
                    // delete a new stack that failed to create
                    try {
                        await Settings.CfnClient.DeleteStackAsync(new DeleteStackRequest {
                            StackName = stackName
                        });
                    } catch { }
                }
                return(success);
            } finally {
                try {
                    await Settings.CfnClient.DeleteChangeSetAsync(new DeleteChangeSetRequest {
                        ChangeSetName = response.Id
                    });
                } catch { }
            }

            // local function
            string TranslateLogicalIdToFullName(string logicalId)
            {
                var fullName = logicalId;

                manifest.ResourceNameMappings?.TryGetValue(logicalId, out fullName);
                return(fullName ?? logicalId);
            }
        }
        //--- Methods ---
        public async Task <bool> DeployChangeSetAsync(
            ModuleManifest manifest,
            ModuleNameMappings nameMappings,
            ModuleLocation moduleLocation,
            string stackName,
            bool allowDataLoss,
            bool protectStack,
            List <CloudFormationParameter> parameters
            )
        {
            var now = DateTime.UtcNow;

            // check if cloudformation stack already exists and is in a final state
            Console.WriteLine();
            Console.WriteLine($"Deploying stack: {stackName} [{moduleLocation.ModuleInfo}]");
            var mostRecentStackEventId = await Settings.CfnClient.GetMostRecentStackEventIdAsync(stackName);

            // validate template (must have been copied to deployment bucket at this stage)
            if (moduleLocation.SourceBucketName != Settings.DeploymentBucketName)
            {
                LogError($"module source must match the deployment tier S3 bucket (EXPECTED: {Settings.DeploymentBucketName}, FOUND: {moduleLocation.SourceBucketName})");
                return(false);
            }
            ValidateTemplateResponse validation;

            try {
                validation = await Settings.CfnClient.ValidateTemplateAsync(new ValidateTemplateRequest {
                    TemplateURL = moduleLocation.ModuleTemplateUrl
                });
            } catch (AmazonCloudFormationException e) {
                LogError($"{e.Message} (url: {moduleLocation.ModuleTemplateUrl})");
                return(false);
            }

            // create change-set
            var success        = false;
            var changeSetName  = $"{moduleLocation.ModuleInfo.FullName.Replace(".", "-")}-{now:yyyy-MM-dd-hh-mm-ss}";
            var updateOrCreate = (mostRecentStackEventId != null) ? "update" : "create";
            var capabilities   = validation.Capabilities.Any()
                ? "[" + string.Join(", ", validation.Capabilities) + "]"
                : "";

            Console.WriteLine($"=> Stack {updateOrCreate} initiated for {stackName} {capabilities}");
            var response = await Settings.CfnClient.CreateChangeSetAsync(new CreateChangeSetRequest {
                Capabilities  = validation.Capabilities,
                ChangeSetName = changeSetName,
                ChangeSetType = (mostRecentStackEventId != null) ? ChangeSetType.UPDATE : ChangeSetType.CREATE,
                Description   = $"Stack {updateOrCreate} {moduleLocation.ModuleInfo.FullName} (v{moduleLocation.ModuleInfo.Version})",
                Parameters    = new List <CloudFormationParameter>(parameters)
                {
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentPrefix",
                        ParameterValue = Settings.TierPrefix
                    },
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentPrefixLowercase",
                        ParameterValue = Settings.TierPrefix.ToLowerInvariant()
                    },
                    new CloudFormationParameter {
                        ParameterKey   = "DeploymentBucketName",
                        ParameterValue = Settings.DeploymentBucketName
                    }
                },
                StackName   = stackName,
                TemplateURL = moduleLocation.ModuleTemplateUrl,
                Tags        = Settings.GetCloudFormationStackTags(moduleLocation.ModuleInfo.FullName, stackName)
            });

            try {
                var changes = await WaitForChangeSetAsync(response.Id);

                if (changes == null)
                {
                    return(false);
                }
                if (!changes.Any())
                {
                    Console.WriteLine("=> No stack update required");
                    return(true);
                }

                //  changes
                if (!allowDataLoss)
                {
                    var lossyChanges = DetectLossyChanges(changes);
                    if (lossyChanges.Any())
                    {
                        LogError("one or more resources could be replaced or deleted; use --allow-data-loss to proceed");
                        Console.WriteLine("=> WARNING: detected potential replacement and data-loss in the following resources");
                        foreach (var lossy in lossyChanges)
                        {
                            Console.WriteLine($"{(lossy.ResourceChange.Replacement == Replacement.True ? "ALWAYS" : "CONDITIONAL"),-12} {lossy.ResourceChange.ResourceType,-55} {TranslateLogicalIdToFullName(lossy.ResourceChange.LogicalResourceId)}");
                        }
                        return(false);
                    }
                }

                // execute change-set
                await Settings.CfnClient.ExecuteChangeSetAsync(new ExecuteChangeSetRequest {
                    ChangeSetName = changeSetName,
                    StackName     = stackName
                });

                var outcome = await Settings.CfnClient.TrackStackUpdateAsync(stackName, response.StackId, mostRecentStackEventId, nameMappings, LogError);

                if (outcome.Success)
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} finished");
                    ShowStackResult(outcome.Stack);
                    success = true;
                }
                else
                {
                    Console.WriteLine($"=> Stack {updateOrCreate} FAILED");
                }

                // optionally enable stack protection
                if (success)
                {
                    // on success, protect the stack if requested
                    if (protectStack)
                    {
                        await Settings.CfnClient.UpdateTerminationProtectionAsync(new UpdateTerminationProtectionRequest {
                            EnableTerminationProtection = protectStack,
                            StackName = stackName
                        });
                    }
                }
                else if (mostRecentStackEventId == null)
                {
                    // delete a new stack that failed to create
                    try {
                        await Settings.CfnClient.DeleteStackAsync(new DeleteStackRequest {
                            StackName = stackName
                        });
                    } catch { }
                }
                return(success);
            } finally {
                try {
                    await Settings.CfnClient.DeleteChangeSetAsync(new DeleteChangeSetRequest {
                        ChangeSetName = response.Id
                    });
                } catch { }
            }

            // local function
            string TranslateLogicalIdToFullName(string logicalId)
            {
                var fullName = logicalId;

                nameMappings?.ResourceNameMappings.TryGetValue(logicalId, out fullName);
                return(fullName ?? logicalId);
            }
        }
Ejemplo n.º 7
0
 protected ClassicModule(float priority, ModuleLocation modloc) : base(priority)
 {
     ModLoc = modloc;
 }