private void AddItem(AModuleItem item)
        {
            switch (item)
            {
            case VariableItem _:
            case PackageItem _:
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                else if (item.IsStackOutput)
                {
                    AddStackOutput(item);
                }
                break;

            case ResourceItem resourceItem:
                var deletionPolicy = (resourceItem.DeletionPolicy != null)
                    ? (Humidifier.DeletionPolicy?)Enum.Parse <Humidifier.DeletionPolicy>(resourceItem.DeletionPolicy, ignoreCase: true)
                    : null;
                _stack.Add(
                    resourceItem.LogicalId,
                    resourceItem.Resource,
                    resourceItem.Condition,
                    dependsOn: resourceItem.DependsOn.ToArray(),
                    deletionPolicy: deletionPolicy
                    );
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                else if (item.IsStackOutput)
                {
                    AddStackOutput(item);
                }
                break;

            case ParameterItem parameterItem:
                _stack.Add(parameterItem.LogicalId, parameterItem.Parameter);
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                else if (item.IsStackOutput)
                {
                    AddStackOutput(item);
                }
                break;

            case FunctionItem functionItem:
                _stack.Add(
                    functionItem.LogicalId,
                    functionItem.Function,
                    functionItem.Condition,
                    dependsOn: functionItem.DependsOn.ToArray()
                    );
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                else if (item.IsStackOutput)
                {
                    AddStackOutput(item);
                }
                break;

            case ConditionItem conditionItem:
                _stack.Add(conditionItem.LogicalId, new Humidifier.Condition(conditionItem.Reference));
                break;

            case MappingItem mappingItem: {
                var mapping = new Humidifier.Mapping();
                foreach (var level1Mapping in mappingItem.Mapping)
                {
                    mapping[level1Mapping.Key] = level1Mapping.Value.ToDictionary(
                        level2Mapping => level2Mapping.Key,
                        level2Mapping => level2Mapping.Value
                        );
                }
                _stack.Add(mappingItem.LogicalId, mapping);
            }
            break;

            case ResourceTypeItem resourceTypeItem:
                _stack.Add(resourceTypeItem.LogicalId, new Humidifier.Output {
                    Description = resourceTypeItem.Description,
                    Value       = _module.Items.First(i => i.LogicalId == resourceTypeItem.Handler).GetExportReference(),
                    Export      = new Dictionary <string, dynamic> {
                        ["Name"] = Humidifier.Fn.Sub($"${{DeploymentPrefix}}{resourceTypeItem.CustomResourceType}")
                    }
                });
                break;

            case AppItem _:

                // nothing to do
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(item), item, "unknown parameter type");
            }

            // local functions
            void AddExport(AModuleItem exportItem)
            {
                var value = exportItem.GetExportReference();

                // TODO (2020-07-14, bjorg): add support for negated condition (requires us to generate an intermediate condition)
                //  !If [ Condition, !Ref AWS::NoValue, !Ref Value ]

                // check if this is a conditional public value
                if (
                    TryGetFnIf(value, out var condition, out var ifTrue, out var ifFalse) &&
                    TryGetFnRef(ifFalse, out var key) &&
                    (key == "AWS::NoValue")
                    )
                {
                    _stack.Add(exportItem.LogicalId, new Humidifier.Output {
                        Description = exportItem.Description,
                        Condition   = condition,
                        Value       = ifTrue,
                        Export      = new Dictionary <string, dynamic> {
                            ["Name"] = Humidifier.Fn.Sub($"${{AWS::StackName}}::{exportItem.FullName}")
                        }
                    });
                }
        private void ConvertItem(AModuleItem parent, int index, ModuleItemNode node, IEnumerable <string> expectedTypes)
        {
            var type = DeterminNodeType("item", index, node, ModuleItemNode.FieldCombinations, expectedTypes);

            switch (type)
            {
            case "Parameter":
                AtLocation(node.Parameter, () => {
                    // validation
                    if (node.Properties != null)
                    {
                        Validate(node.Type != null, "'Type' attribute is required");
                    }
                    Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types");
                    Validate(parent == null, "'Parameter' cannot be nested");

                    // create input parameter item
                    _builder.AddParameter(
                        name: node.Parameter,
                        section: node.Section,
                        label: node.Label,
                        description: node.Description,
                        type: node.Type ?? "String",
                        scope: ConvertScope(node.Scope),
                        noEcho: node.NoEcho,
                        defaultValue: node.Default,
                        constraintDescription: node.ConstraintDescription,
                        allowedPattern: node.AllowedPattern,
                        allowedValues: node.AllowedValues,
                        maxLength: node.MaxLength,
                        maxValue: node.MaxValue,
                        minLength: node.MinLength,
                        minValue: node.MinValue,
                        allow: node.Allow,
                        properties: ParseToDictionary("Properties", node.Properties),
                        arnAttribute: node.DefaultAttribute,
                        encryptionContext: node.EncryptionContext,
                        pragmas: node.Pragmas
                        );
                });
                break;

            case "Import":
                AtLocation(node.Import, () => {
                    // validation
                    Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types");
                    Validate(node.Module != null, "missing 'Module' attribute");

                    // create input parameter item
                    _builder.AddImport(
                        parent: parent,
                        name: node.Import,
                        description: node.Description,
                        type: node.Type ?? "String",
                        scope: ConvertScope(node.Scope),
                        allow: node.Allow,
                        module: node.Module ?? "Bad.Module",
                        encryptionContext: node.EncryptionContext,
                        out var _
                        );
                });
예제 #3
0
        private void ConvertItem(AModuleItem parent, int index, ModuleItemNode node, IEnumerable <string> expectedTypes)
        {
            var type = DeterminNodeType("item", index, node, ModuleItemNode.FieldCombinations, expectedTypes);

            switch (type)
            {
            case "Parameter":
                AtLocation(node.Parameter, () => {
                    // validation
                    if (node.Properties != null)
                    {
                        Validate(node.Type != null, "'Type' attribute is required");
                    }
                    Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types");
                    Validate(parent == null, "'Parameter' cannot be nested");

                    // create input parameter item
                    _builder.AddParameter(
                        name: node.Parameter,
                        section: node.Section,
                        label: node.Label,
                        description: node.Description,
                        type: node.Type ?? "String",
                        scope: ConvertScope(node.Scope),
                        noEcho: node.NoEcho,
                        defaultValue: node.Default,
                        constraintDescription: node.ConstraintDescription,
                        allowedPattern: node.AllowedPattern,
                        allowedValues: node.AllowedValues,
                        maxLength: node.MaxLength,
                        maxValue: node.MaxValue,
                        minLength: node.MinLength,
                        minValue: node.MinValue,
                        allow: node.Allow,
                        properties: ParseToDictionary("Properties", node.Properties),
                        arnAttribute: node.DefaultAttribute,
                        encryptionContext: node.EncryptionContext,
                        pragmas: node.Pragmas
                        );
                });
                break;

            case "Import":
                AtLocation(node.Import, () => {
                    // validation
                    Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types");
                    Validate(node.Module != null, "missing 'Module' attribute");

                    // create input parameter item
                    _builder.AddImport(
                        parent: parent,
                        name: node.Import,
                        description: node.Description,
                        type: node.Type ?? "String",
                        scope: ConvertScope(node.Scope),
                        allow: node.Allow,
                        module: node.Module ?? "Bad.Module",
                        encryptionContext: node.EncryptionContext
                        );
                });
                break;

            case "Variable":
                AtLocation(node.Variable, () => {
                    // validation
                    Validate(node.Value != null, "missing 'Value' attribute");
                    Validate((node.EncryptionContext == null) || (node.Type == "Secret"), "item must have Type 'Secret' to use 'EncryptionContext' section");
                    Validate((node.Type != "Secret") || !(node.Value is IList <object>), "item with type 'Secret' cannot have a list of values");

                    // create variable item
                    _builder.AddVariable(
                        parent: parent,
                        name: node.Variable,
                        description: node.Description,
                        type: node.Type ?? "String",
                        scope: ConvertScope(node.Scope),
                        value: node.Value ?? "",
                        allow: null,
                        encryptionContext: node.EncryptionContext
                        );
                });
                break;

            case "Group":
                AtLocation(node.Group, () => {
                    // create namespace item
                    var result = _builder.AddVariable(
                        parent: parent,
                        name: node.Group,
                        description: node.Description,
                        type: "String",
                        scope: null,
                        value: "",
                        allow: null,
                        encryptionContext: null
                        );

                    // recurse
                    ConvertItems(result, expectedTypes);
                });
                break;

            case "Resource":
                AtLocation(node.Resource, () => {
                    if (node.Value != null)
                    {
                        // validation
                        Validate((node.Allow == null) || (node.Type == null) || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types");
                        Validate(node.If == null, "'If' attribute cannot be used with a referenced resource");
                        Validate(node.Properties == null, "'Properties' section cannot be used with a referenced resource");
                        if (node.Value is IList <object> values)
                        {
                            foreach (var arn in values)
                            {
                                ValidateARN(arn);
                            }
                        }
                        else
                        {
                            ValidateARN(node.Value);
                        }

                        // create variable item
                        _builder.AddVariable(
                            parent: parent,
                            name: node.Resource,
                            description: node.Description,
                            type: node.Type ?? "String",
                            scope: ConvertScope(node.Scope),
                            value: node.Value,
                            allow: node.Allow,
                            encryptionContext: node.EncryptionContext
                            );
                    }
                    else
                    {
                        // validation
                        Validate(node.Type != null, "missing 'Type' attribute");
                        Validate((node.Allow == null) || ResourceMapping.IsCloudFormationType(node.Type ?? ""), "'Allow' attribute can only be used with AWS resource types");

                        // create resource item
                        _builder.AddResource(
                            parent: parent,
                            name: node.Resource,
                            description: node.Description,
                            type: node.Type ?? "AWS",
                            scope: ConvertScope(node.Scope),
                            allow: node.Allow,
                            properties: ParseToDictionary("Properties", node.Properties),
                            dependsOn: ConvertToStringList(node.DependsOn),
                            arnAttribute: node.DefaultAttribute,
                            condition: node.If,
                            pragmas: node.Pragmas
                            );
                    }
                });
                break;

            case "Nested":
                AtLocation(node.Nested, () => {
                    // validation
                    if (node.Module == null)
                    {
                        LogError("missing 'Module' attribute");
                    }
                    else if (!ModuleInfo.TryParse(node.Module, out var moduleInfo))
                    {
                        LogError("invalid value for 'Module' attribute");
                    }
                    else
                    {
                        // create nested module item
                        _builder.AddNestedModule(
                            parent: parent,
                            name: node.Nested,
                            description: node.Description,
                            moduleInfo: moduleInfo,
                            scope: ConvertScope(node.Scope),
                            dependsOn: node.DependsOn,
                            parameters: node.Parameters
                            );
                    }
                });
                break;

            case "Package":

                // package resource
                AtLocation(node.Package, () => {
                    // discover files to package
                    var files = new List <KeyValuePair <string, string> >();
                    if (node.Files != null)
                    {
                        string folder;
                        string filePattern;
                        SearchOption searchOption;
                        var packageFiles = Path.Combine(Settings.WorkingDirectory, node.Files);
                        if ((packageFiles.EndsWith("/", StringComparison.Ordinal) || Directory.Exists(packageFiles)))
                        {
                            folder       = Path.GetFullPath(packageFiles);
                            filePattern  = "*";
                            searchOption = SearchOption.AllDirectories;
                        }
                        else
                        {
                            folder       = Path.GetDirectoryName(packageFiles);
                            filePattern  = Path.GetFileName(packageFiles);
                            searchOption = SearchOption.TopDirectoryOnly;
                        }
                        if (Directory.Exists(folder))
                        {
                            foreach (var filePath in Directory.GetFiles(folder, filePattern, searchOption))
                            {
                                var relativeFilePathName = Path.GetRelativePath(folder, filePath);
                                files.Add(new KeyValuePair <string, string>(relativeFilePathName, filePath));
                            }
                            files = files.OrderBy(file => file.Key).ToList();
                        }
                        else
                        {
                            LogError($"cannot find folder '{Path.GetRelativePath(Settings.WorkingDirectory, folder)}'");
                        }
                    }
                    else
                    {
                        LogError("missing 'Files' attribute");
                    }

                    // create package resource item
                    _builder.AddPackage(
                        parent: parent,
                        name: node.Package,
                        description: node.Description,
                        scope: ConvertScope(node.Scope),
                        files: files
                        );
                });
                break;

            case "Function":
                AtLocation(node.Function, () => {
                    // validation
                    Validate(node.Memory != null, "missing 'Memory' attribute");
                    Validate(int.TryParse(node.Memory, out _), "invalid 'Memory' value");
                    Validate(node.Timeout != null, "missing 'Timeout' attribute");
                    Validate(int.TryParse(node.Timeout, out _), "invalid 'Timeout' value");
                    ValidateFunctionSource(node.Sources ?? new FunctionSourceNode[0]);

                    // determine function type
                    var project  = node.Project;
                    var language = node.Language;
                    var runtime  = node.Runtime;
                    var handler  = node.Handler;
                    DetermineFunctionType(node.Function, ref project, ref language, ref runtime, ref handler);

                    // create function item
                    var sources = AtLocation("Sources", () => node.Sources
                                             ?.Select((source, eventIndex) => ConvertFunctionSource(node, eventIndex, source))
                                             .Where(evt => evt != null)
                                             .ToList()
                                             );
                    _builder.AddFunction(
                        parent: parent,
                        name: node.Function,
                        description: node.Description,
                        scope: ConvertScope(node.Scope),
                        project: project,
                        language: language,
                        environment: node.Environment,
                        sources: sources,
                        condition: node.If,
                        pragmas: node.Pragmas,
                        timeout: node.Timeout,
                        runtime: runtime,
                        memory: node.Memory,
                        handler: handler,
                        properties: ParseToDictionary("Properties", node.Properties)
                        );
                });
                break;

            case "Condition":
                AtLocation(node.Condition, () => {
                    AtLocation("Value", () => {
                        Validate(node.Value != null, "missing 'Value' attribute");
                    });
                    _builder.AddCondition(
                        parent: parent,
                        name: node.Condition,
                        description: node.Description,
                        value: node.Value
                        );
                });
                break;

            case "Mapping":
                AtLocation(node.Mapping, () => {
                    IDictionary <string, IDictionary <string, string> > topLevelResults = new Dictionary <string, IDictionary <string, string> >();
                    if (node.Value is IDictionary topLevelDictionary)
                    {
                        AtLocation("Value", () => {
                            Validate(topLevelDictionary.Count > 0, "missing top-level mappings");

                            // iterate over top-level dictionary
                            foreach (DictionaryEntry topLevel in topLevelDictionary)
                            {
                                AtLocation((string)topLevel.Key, () => {
                                    var secondLevelResults = new Dictionary <string, string>();
                                    topLevelResults[(string)topLevel.Key] = secondLevelResults;

                                    // convert top-level entry
                                    if (topLevel.Value is IDictionary secondLevelDictionary)
                                    {
                                        Validate(secondLevelDictionary.Count > 0, "missing second-level mappings");

                                        // iterate over second-level dictionary
                                        foreach (DictionaryEntry secondLevel in secondLevelDictionary)
                                        {
                                            AtLocation((string)secondLevel.Key, () => {
                                                // convert second-level entry
                                                if (secondLevel.Value is string secondLevelValue)
                                                {
                                                    secondLevelResults[(string)secondLevel.Key] = secondLevelValue;
                                                }
                                                else
                                                {
                                                    LogError("invalid value");
                                                }
                                            });
                                        }
                                    }
예제 #4
0
        private void AddItem(AModuleItem item)
        {
            switch (item)
            {
            case VariableItem _:
            case PackageItem _:
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                break;

            case ResourceItem resourceItem:
                _stack.Add(
                    resourceItem.LogicalId,
                    resourceItem.Resource,
                    resourceItem.Condition,
                    dependsOn: resourceItem.DependsOn.ToArray()
                    );
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                break;

            case ParameterItem parameterItem:
                _stack.Add(parameterItem.LogicalId, parameterItem.Parameter);
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                break;

            case FunctionItem functionItem:
                _stack.Add(
                    functionItem.LogicalId,
                    functionItem.Function,
                    functionItem.Condition,
                    dependsOn: functionItem.DependsOn.ToArray()
                    );
                if (item.IsPublic)
                {
                    AddExport(item);
                }
                break;

            case ConditionItem conditionItem:
                _stack.Add(conditionItem.LogicalId, new Condition(conditionItem.Reference));
                break;

            case MappingItem mappingItem: {
                var mapping = new Mapping();
                foreach (var level1Mapping in mappingItem.Mapping)
                {
                    mapping[level1Mapping.Key] = level1Mapping.Value.ToDictionary(
                        level2Mapping => level2Mapping.Key,
                        level2Mapping => level2Mapping.Value
                        );
                }
                _stack.Add(mappingItem.LogicalId, mapping);
            }
            break;

            case ResourceTypeItem resourceTypeItem:
                _stack.Add(resourceTypeItem.LogicalId, new Humidifier.Output {
                    Description = resourceTypeItem.Description,
                    Value       = _module.Items.First(i => i.LogicalId == resourceTypeItem.Handler).GetExportReference(),
                    Export      = new Dictionary <string, dynamic> {
                        ["Name"] = Fn.Sub($"${{DeploymentPrefix}}{resourceTypeItem.CustomResourceType}")
                    }
                });
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(item), item, "unknown parameter type");
            }

            // local functions
            void AddExport(AModuleItem exportItem)
            {
                _stack.Add(exportItem.LogicalId, new Humidifier.Output {
                    Description = exportItem.Description,
                    Value       = exportItem.GetExportReference(),
                    Export      = new Dictionary <string, dynamic> {
                        ["Name"] = Fn.Sub($"${{AWS::StackName}}::{exportItem.FullName}")
                    }
                });
            }
        }