Exemple #1
0
        public async Task <object> ExecuteOperations(ExecutionContext parentContext, IList <WorkflowModel.Operation> operations)
        {
            var    cumulativeValues = parentContext.Values;
            object resultOut        = null;

            foreach (var operation in operations)
            {
                var childContext = parentContext.CreateChildContext(operation, cumulativeValues);

                await ExecuteOperation(childContext);

                if (childContext.ValuesOut != null)
                {
                    cumulativeValues = MergeUtils.Merge(childContext.ValuesOut, cumulativeValues);
                    resultOut        = MergeUtils.Merge(childContext.ValuesOut, resultOut);
                }
            }

            return(resultOut);
        }
Exemple #2
0
        private async Task ExecuteOperation(ExecutionContext context)
        {
            var operation = context.Operation;

            if (context.Operation.@foreach == null)
            {
                await ExecuteOperationInner(context);
            }
            else
            {
                var foreachContexts     = new List <ExecutionContext>();
                var foreachValuesInList = ProcessValuesForeachIn([email protected], context.Values);

                foreach (var foreachValuesIn in foreachValuesInList)
                {
                    var foreachContext = context.CreateChildContext(operation, MergeUtils.Merge(foreachValuesIn, context.Values));
                    foreachContexts.Add(foreachContext);
                    await ExecuteOperationInner(foreachContext);
                }

                var valuesOut = default(object);
                if ([email protected] != null)
                {
                    valuesOut = ProcessValuesForeachOut([email protected], foreachContexts.Select(foreachContext => foreachContext.Values).ToList());
                }
                else
                {
                    foreach (var foreachValuesOut in foreachContexts.Select(foreachContext => foreachContext.ValuesOut))
                    {
                        valuesOut = MergeUtils.Merge(foreachValuesOut, valuesOut);
                    }
                }

                context.AddValuesOut(valuesOut);
            }
        }
Exemple #3
0
        private async Task ExecuteOperation(ExecutionContext context)
        {
            var operation = context.Operation;

            if (operation.values != null)
            {
                context.AddValuesIn(ProcessValues(operation.values, context.Values));
            }

            var patternOkay = context.PatternMatcher.IsMatch(context.Path);

            var message = ConvertToString(ProcessValues(operation.message, context.Values));

            var conditionOkay = true;

            if (!string.IsNullOrEmpty(operation.condition))
            {
                var conditionResult = _jmesPathQuery.Search(operation.condition, context.Values);
                conditionOkay = ConditionBoolean(conditionResult);
            }

            for (var shouldExecute = patternOkay && conditionOkay; shouldExecute; shouldExecute = await EvaluateRepeat(context))
            {
                if (!string.IsNullOrEmpty(message))
                {
                    _console.WriteLine();
                    _console.WriteLine($"{new string(' ', context.Indent * 2)}- {message.Color(ConsoleColor.Cyan)}");
                }

                var debugPath = Path.Combine(OutputDirectory.Required(), "logs", $"{++_operationCount:000}-{new string('-', context.Indent * 2)}{new string((message ?? operation.write ?? operation.request ?? operation.template ?? string.Empty).Select(ch => char.IsLetterOrDigit(ch) ? ch : '-').ToArray())}.yaml");
                Directory.CreateDirectory(Path.GetDirectoryName(debugPath));
                using (var writer = _secretTracker.FilterTextWriter(File.CreateText(debugPath)))
                {
                    var logentry = new Dictionary <object, object>
                    {
                        {
                            "operation",
                            new Dictionary <object, object>
                            {
                                { "message", message },
                                { "target", operation.target },
                                { "condition", operation.condition },
                                { "repeat", operation.repeat },
                                { "request", operation.request },
                                { "template", operation.template },
                                { "write", operation.write },
                            }
                        },
                        { "valuesIn", context.ValuesIn },
                        { "valuesOut", context.ValuesOut },
                        { "request", null },
                        { "response", null },
                        { "cumulativeValues", context.Values },
                    };
                    try
                    {
                        object result = null;

                        // First special type of operation - executing a request
                        if (!string.IsNullOrWhiteSpace(operation.request))
                        {
                            WorkflowModel.Request request = context.TemplateEngine.Render <WorkflowModel.Request>(
                                operation.request,
                                context.Values);

                            logentry["request"] = request;

                            HttpAuthentication auth = null;
                            if (request.auth != null)
                            {
                                // TODO: remove these defaults
                                auth = new HttpAuthentication
                                {
                                    tenant      = request?.auth?.tenant ?? "common",
                                    resourceId  = request?.auth?.resource ?? "499b84ac-1321-427f-aa17-267ca6975798",
                                    clientId    = request?.auth?.client ?? "e8f3cc86-b3b2-4ebb-867c-9c314925b384",
                                    interactive = IsInteractive
                                };
                            }

                            var client = _clientFactory.Create(auth);

                            var method = new HttpMethod(request.method ?? "GET");
                            if (IsDryRun && method.Method != "GET")
                            {
                                _console.WriteLine($"Skipping {method.Method.ToString().Color(ConsoleColor.DarkYellow)} {request.url}");
                            }
                            else
                            {
                                try
                                {
                                    var jsonRequest = new JsonRequest
                                    {
                                        method  = method,
                                        url     = request.url,
                                        headers = request.headers,
                                        body    = request.body,
                                        secret  = request.secret,
                                    };

                                    var jsonResponse = await client.SendAsync(jsonRequest);

                                    if ((int)jsonResponse.status >= 400)
                                    {
                                        throw new ApplicationException($"Request failed with status code {jsonResponse.status}");
                                    }

                                    result = new WorkflowModel.Response
                                    {
                                        status  = (int)jsonResponse.status,
                                        headers = jsonResponse.headers,
                                        body    = jsonResponse.body,
                                    };

                                    logentry["response"] = result;
                                }
                                catch
                                {
                                    // TODO - retry logic here?
                                    throw;
                                }
                            }
                        }

                        // Second special type of operation - rendering a template
                        if (!string.IsNullOrWhiteSpace(operation.template))
                        {
                            if (!string.IsNullOrEmpty(operation.write))
                            {
                                var targetPath = Path.Combine(OutputDirectory.Required(), operation.write);

                                Directory.CreateDirectory(Path.GetDirectoryName(targetPath));

                                using (var targetWriter = File.CreateText(targetPath))
                                {
                                    if (!string.IsNullOrWhiteSpace(operation.template))
                                    {
                                        context.TemplateEngine.Render(operation.template, context.Values, targetWriter);
                                    }
                                }
                            }
                            else
                            {
                                result = context.TemplateEngine.Render <object>(operation.template, context.Values);
                            }
                        }

                        // Third special type of operation - nested operations
                        if (operation.operations != null)
                        {
                            result = await ExecuteOperations(context, operation.operations);
                        }

                        // If output is specifically stated - use it to query
                        if (operation.output != null)
                        {
                            if (operation.operations != null)
                            {
                                // for nested operations, output expressions can pull in the current operation's cumulative values as well
                                context.AddValuesOut(ProcessValues(operation.output, MergeUtils.Merge(result, context.Values) ?? context.Values));
                            }
                            else if (result != null)
                            {
                                // for request and template operations, the current operation result is a well-known property to avoid collisions
                                var merged = MergeUtils.Merge(new Dictionary <object, object> {
                                    { "result", result }
                                }, context.Values);

                                context.AddValuesOut(ProcessValues(operation.output, merged));
                            }
                            else
                            {
                                // there are no values coming out of this operation - output queries are based only on cumulative values
                                context.AddValuesOut(ProcessValues(operation.output, context.Values));
                            }
                        }

                        if (operation.@throw != null)
                        {
                            var throwMessage = ConvertToString(ProcessValues([email protected], context.Values));
                            throwMessage = string.IsNullOrEmpty(throwMessage) ? message : throwMessage;

                            var throwDetails = ProcessValues([email protected], context.Values);

                            _console.WriteLine(throwMessage.Color(ConsoleColor.DarkRed));
                            if (throwDetails != null)
                            {
                                _console.WriteLine(_serializers.YamlSerializer.Serialize(throwDetails).Color(ConsoleColor.DarkRed));
                            }

                            throw new OperationException(string.IsNullOrEmpty(throwMessage) ? message : throwMessage)
                                  {
                                      Details = throwDetails
                                  };
                        }

                        // otherwise if output is unstated, and there are nested operations with output - those flows back as effective output
                        if (operation.output == null && operation.operations != null && result != null)
                        {
                            context.AddValuesOut(result);
                        }
                    }
                    catch (Exception ex)
                    {
                        logentry["error"] = new Dictionary <object, object>
                        {
                            { "type", ex.GetType().FullName },
                            { "message", ex.Message },
                            { "stack", ex.StackTrace },
                        };

                        throw;
                    }
                    finally
                    {
                        logentry["valuesIn"]         = context?.ValuesIn;
                        logentry["valuesOut"]        = context?.ValuesOut;
                        logentry["cumulativeValues"] = context?.Values;
                        _serializers.YamlSerializer.Serialize(writer, logentry);
                    }
                }
            }
        }
Exemple #4
0
        public async Task <int> ExecuteCore(bool generateOnly)
        {
            if (!OutputDirectory.HasValue())
            {
                OutputDirectory.TryParse($"_output");
            }

            var logsPath = Path.Combine(OutputDirectory.Required(), "logs");

            if (Directory.Exists(logsPath))
            {
                foreach (var file in Directory.EnumerateFiles(logsPath))
                {
                    File.Delete(file);
                }
            }

            var blueprint = _blueprintManager.GetBlueprintPackage(Blueprint.Required());

            if (blueprint == null)
            {
                throw new ApplicationException($"Unable to locate blueprint {Blueprint.Required()}");
            }

            var templateEngine = _templateEngineFactory.Create(new TemplateEngineOptions
            {
                FileSystem = new BlueprintPackageFileSystem(blueprint)
            });

            var eachValues = new List <object>();

            if (blueprint.Exists("values.yaml"))
            {
                using (var reader = blueprint.OpenText("values.yaml"))
                {
                    eachValues.Add(_serializers.YamlDeserializer.Deserialize(reader));
                }
            }

            var defaultValuesFiles =
                File.Exists("atlas-values.yaml") ? new[] { "atlas-values.yaml" } :
            File.Exists("values.yaml") ? new[] { "values.yaml" } :
            new string[0];

            foreach (var valuesFile in Values.OptionalMany(defaultValuesFiles))
            {
                using (var reader = File.OpenText(valuesFile))
                {
                    eachValues.Add(_serializers.YamlDeserializer.Deserialize(reader));
                }
            }

            if (Set.HasValue())
            {
                var setValues = new Dictionary <object, object>();

                foreach (var set in Set.Values)
                {
                    var parts = set.Split('=', 2);
                    if (parts.Length == 1)
                    {
                        throw new ApplicationException("Equal sign required when using the option --set name=value");
                    }

                    var name     = parts[0];
                    var value    = parts[1];
                    var segments = name.Split('.');
                    if (segments.Any(segment => string.IsNullOrEmpty(segment)))
                    {
                        throw new ApplicationException("Name must not have empty segments when using the option --set name=value");
                    }

                    var cursor = (IDictionary <object, object>)setValues;
                    foreach (var segment in segments.Reverse().Skip(1).Reverse())
                    {
                        if (cursor.TryGetValue(segment, out var child) && child is IDictionary <object, object> )
                        {
                            cursor = (IDictionary <object, object>)child;
                        }
                        else
                        {
                            child           = new Dictionary <object, object>();
                            cursor[segment] = child;
                            cursor          = (IDictionary <object, object>)child;
                        }
                    }

                    cursor[segments.Last()] = value;
                }

                eachValues.Add(setValues);
            }

            IDictionary <object, object> values = new Dictionary <object, object>();

            foreach (var addValues in eachValues)
            {
                values = (IDictionary <object, object>)MergeUtils.Merge(addValues, values) ?? values;
            }

            object model;

            var modelTemplate = "model.yaml";
            var modelExists   = blueprint.Exists(modelTemplate);

            if (modelExists)
            {
                model = templateEngine.Render <object>(modelTemplate, values);
            }
            else
            {
                model = values;
            }

            var workflowTemplate = "workflow.yaml";
            var workflowContents = new StringBuilder();

            using (var workflowWriter = new StringWriter(workflowContents))
            {
                templateEngine.Render(workflowTemplate, model, workflowWriter);
            }

            // NOTE: the workflow is rendered BEFORE writing these output files because it may contain
            // calls to the "secret" helper which will redact tokens that might have been provided in values

            // write values to output folder
            GenerateOutput("values.yaml", writer => _serializers.YamlSerializer.Serialize(writer, values));

            if (modelExists)
            {
                // write normalized values to output folder
                GenerateOutput(modelTemplate, writer => templateEngine.Render(modelTemplate, values, writer));
            }

            // write workflow to output folder
            GenerateOutput("workflow.yaml", writer => writer.Write(workflowContents.ToString()));

            var workflow = _serializers.YamlDeserializer.Deserialize <WorkflowModel>(new StringReader(workflowContents.ToString()));

            var patternMatcher = _patternMatcherFactory.Create(Target.Values.Any() ? Target.Values : new List <string>()
            {
                "/**"
            });

            if (generateOnly == false)
            {
                var context = new ExecutionContext(templateEngine, patternMatcher, null);
                context.AddValuesIn(ProcessValues(workflow.values, context.Values));

                var resultOut = await ExecuteOperations(context, workflow.operations);

                if (workflow.output != null)
                {
                    context.AddValuesOut(ProcessValues(workflow.output, MergeUtils.Merge(resultOut, context.Values) ?? context.Values));
                }
                else
                {
                    context.AddValuesOut(resultOut);
                }

                if (context.ValuesOut != null)
                {
                    GenerateOutput("output.yaml", writer => _serializers.YamlSerializer.Serialize(writer, context.ValuesOut));

                    using (var writer = _secretTracker.FilterTextWriter(_console.Out))
                    {
                        _serializers.YamlSerializer.Serialize(writer, context.ValuesOut);
                    }
                }
            }

            return(0);
        }
Exemple #5
0
        public async Task <int> ExecuteCore(bool generateOnly)
        {
            if (!OutputDirectory.HasValue())
            {
                OutputDirectory.TryParse($"_output");
            }

            var logsPath = Path.Combine(OutputDirectory.Required(), "logs");

            if (Directory.Exists(logsPath))
            {
                foreach (var file in Directory.EnumerateFiles(logsPath))
                {
                    File.Delete(file);
                }
            }

            var generatedPath = Path.Combine(OutputDirectory.Required(), "generated");

            if (Directory.Exists(generatedPath))
            {
                foreach (var file in Directory.EnumerateFiles(generatedPath, "*.*", new EnumerationOptions {
                    RecurseSubdirectories = true
                }))
                {
                    File.Delete(file);
                }

                foreach (var folder in Directory.EnumerateDirectories(generatedPath))
                {
                    Directory.Delete(folder, recursive: true);
                }
            }

            var blueprint = await _blueprintManager.GetBlueprintPackage(Blueprint.Required());

            if (blueprint == null)
            {
                throw new ApplicationException($"Unable to locate blueprint {Blueprint.Required()}");
            }

            var eachValues         = new List <object>();
            var defaultValuesFiles =
                File.Exists("values.yaml") ? new[] { "values.yaml" } :
            new string[0];

            foreach (var valuesFile in Values.OptionalMany(defaultValuesFiles))
            {
                using (var reader = File.OpenText(valuesFile))
                {
                    eachValues.Add(_serializers.YamlDeserializer.Deserialize(reader));
                }
            }

            if (Set.HasValue())
            {
                var setValues = new Dictionary <object, object>();

                foreach (var set in Set.Values)
                {
                    var parts = set.Split('=', 2);
                    if (parts.Length == 1)
                    {
                        throw new ApplicationException("Equal sign required when using the option --set name=value");
                    }

                    var name     = parts[0];
                    var value    = parts[1];
                    var segments = name.Split('.');
                    if (segments.Any(segment => string.IsNullOrEmpty(segment)))
                    {
                        throw new ApplicationException("Name must not have empty segments when using the option --set name=value");
                    }

                    var cursor = (IDictionary <object, object>)setValues;
                    foreach (var segment in segments.Reverse().Skip(1).Reverse())
                    {
                        if (cursor.TryGetValue(segment, out var child) && child is IDictionary <object, object> )
                        {
                            cursor = (IDictionary <object, object>)child;
                        }
                        else
                        {
                            child           = new Dictionary <object, object>();
                            cursor[segment] = child;
                            cursor          = (IDictionary <object, object>)child;
                        }
                    }

                    cursor[segments.Last()] = value;
                }

                eachValues.Add(setValues);
            }

            IDictionary <object, object> values = new Dictionary <object, object>();

            foreach (var addValues in eachValues)
            {
                values = (IDictionary <object, object>)MergeUtils.Merge(addValues, values) ?? values;
            }

            var(templateEngine, workflow, effectiveValues) = _workflowLoader.Load(blueprint, values, GenerateOutput);

            if (generateOnly == false)
            {
                var patternMatcher = _patternMatcherFactory.Create(Target.Values.Any() ? Target.Values : new List <string>()
                {
                    "/**"
                });

                var context = new ExecutionContext.Builder()
                              .UseBlueprintPackage(blueprint)
                              .UseTemplateEngine(templateEngine)
                              .UsePatternMatcher(patternMatcher)
                              .SetOutputDirectory(OutputDirectory.Required())
                              .SetNonInteractive(NonInteractive?.HasValue() ?? false)
                              .SetDryRun(DryRun?.HasValue() ?? false)
                              .SetValues(effectiveValues)
                              .Build();

                context.AddValuesIn(_valuesEngine.ProcessValues(workflow.values, context.Values) ?? context.Values);

                var resultOut = await _workflowEngine.ExecuteOperations(context, workflow.operations);

                if (workflow.output != null)
                {
                    context.AddValuesOut(_valuesEngine.ProcessValues(workflow.output, MergeUtils.Merge(resultOut, context.Values) ?? context.Values));
                }
                else
                {
                    context.AddValuesOut(resultOut);
                }

                if (context.ValuesOut != null)
                {
                    GenerateOutput("output.yaml", writer => _serializers.YamlSerializer.Serialize(writer, context.ValuesOut));

                    using (var writer = _secretTracker.FilterTextWriter(_console.Out))
                    {
                        _serializers.YamlSerializer.Serialize(writer, context.ValuesOut);
                    }
                }
            }

            return(0);
        }
Exemple #6
0
 public void BasicTests()
 {
     var data = MergeUtils.Merge(new[] { 1, 2, 3 }, new[] { 3, 4, 5 }, x => x);
 }
Exemple #7
0
        private object ProcessValuesRecursive(object source, IList <object> contexts, bool promoteArrays)
        {
            if (source is IDictionary <object, object> sourceDictionary)
            {
                var arrayIsPromoting = false;
                var arrayLength      = 0;

                var output = new Dictionary <object, object>();
                foreach (var kv in sourceDictionary)
                {
                    var result = ProcessValuesRecursive(kv.Value, contexts, promoteArrays: promoteArrays);
                    output[kv.Key] = result;

                    if (promoteArrays && result is IList <object> resultArray)
                    {
                        if (!arrayIsPromoting)
                        {
                            arrayIsPromoting = true;
                            arrayLength      = resultArray.Count();
                        }
                        else
                        {
                            if (arrayLength != resultArray.Count())
                            {
                                throw new ApplicationException("Foreach arrays must all be same size");
                            }
                        }
                    }
                }

                if (arrayIsPromoting)
                {
                    var arrayOutput = new List <object>();
                    for (var index = 0; index < arrayLength; ++index)
                    {
                        var arrayItem = output.ToDictionary(kv => kv.Key, kv => kv.Value is IList <object> valueArray ? valueArray[index] : kv.Value);
                        arrayOutput.Add(arrayItem);
                    }

                    return(arrayOutput);
                }

                return(output);
            }

            if (source is IList <object> sourceList)
            {
                return(sourceList.Select(value => ProcessValuesRecursive(value, contexts, promoteArrays: promoteArrays)).ToList());
            }

            if (source is string sourceString)
            {
                if (sourceString.StartsWith('(') && sourceString.EndsWith(')'))
                {
                    var expression   = sourceString.Substring(1, sourceString.Length - 2);
                    var mergedResult = default(object);
                    foreach (var context in contexts)
                    {
                        var result = _jmesPathQuery.Search(sourceString, context);
                        if (result is IList <object> resultList && mergedResult is IList <object> mergedList)
                        {
                            mergedResult = mergedList.Concat(resultList).ToList();
                        }
                        else
                        {
                            mergedResult = MergeUtils.Merge(result, mergedResult);
                        }
                    }

                    return(mergedResult);
                }
            }

            return(source);
        }
Exemple #8
0
 public void Test_TwoWayMerge_WithDefaultOpion()
 {
     Assert.Throws(typeof(MergeOperationException), () => MergeUtils.Merge("", "abc", "cde"),
                   "Should throw MergeOperationException");
 }
Exemple #9
0
 public int Test_ResolveConflicts(int left, int right, ConflictResolutionOptions options)
 {
     return(MergeUtils.ResolveConflict(left, right, options));
 }
Exemple #10
0
 public string Test_TwoWayMerge_WithOptions(string origin, string left, string right, ConflictResolutionOptions options)
 {
     return(MergeUtils.Merge(origin, left, right, options));
 }
Exemple #11
0
 public void Test_TwoWayMerge_WithException()
 {
     Assert.Throws(typeof(MergeOperationException), () => MergeUtils.Merge("", "abc", "cde", ConflictResolutionOptions.RaiseException),
                   "Should throw MergeOperationException");
 }
Exemple #12
0
 public void Test_MergeTwoValues_WithDefaultOption()
 {
     Assert.Throws(typeof(MergeOperationException), () => MergeUtils.Merge(1, 2), "Should throw MergeOperationException by default");
 }
Exemple #13
0
 public void Test_MergeTwoRefValues_WithException()
 {
     Assert.Throws(typeof(MergeOperationException), () => MergeUtils.Merge(null, "", ConflictResolutionOptions.RaiseException),
                   "Should throw MergeOperationException");
 }
Exemple #14
0
 public string Test_MergeTwoRefValues_WithOptions(string left, string right, ConflictResolutionOptions options)
 {
     return(MergeUtils.Merge(left, right, options));
 }
Exemple #15
0
 public int Test_MergeTwoValues_WithOptions(int left, int right, ConflictResolutionOptions options)
 {
     return(MergeUtils.Merge(left, right, options));
 }
Exemple #16
0
 public void Test_ResolveConflicts_WithException()
 {
     Assert.Throws(typeof(MergeOperationException),
                   () => MergeUtils.ResolveConflict(1, 2, ConflictResolutionOptions.RaiseException), "Should throw exception");
 }