func MakeFnWith(sources *deepstack.DeepStack, outerRules *template.Rules) template.Rule { return func(path []interface{}, node interface{}) (interface{}, interface{}) { key := interface{}(nil) if len(path) > 0 { key = path[len(path)-1] } raw, ok := singleKey(node, "Fn::With") if !ok { return key, node //passthru } args, ok := collectArgs( raw, func(argsSoFar []interface{}) bool { return len(argsSoFar) < 2 }, func(argsSoFar []interface{}, arg interface{}) (bool, interface{}) { // unconditionally process the argument, in case it needs to be skipped key, node := template.Walk(path, arg, outerRules) if skip, ok := key.(bool); ok && skip { return true, nil } if len(argsSoFar) == 1 { return false, arg // return unprocessed 2nd arg. It's a template. } return false, node }, ) if !ok { return key, node //passthru } var source map[string]interface{} if source, ok = args[0].(map[string]interface{}); !ok { return key, node //passthru } sources.Push(fallbackmap.DeepMap(source)) innerTemplate := interface{}(args[1]) key, generated := template.Walk(path, innerTemplate, outerRules) sources.PopDiscard() return key, interface{}(generated) } }
func MakeFnFor(sources *deepstack.DeepStack, templateRules *template.Rules) template.Rule { return func(path []interface{}, node interface{}) (interface{}, interface{}) { key := interface{}(nil) if len(path) > 0 { key = path[len(path)-1] } raw, ok := singleKey(node, "Fn::For") if !ok { return key, node //passthru } args, ok := collectArgs( raw, func(argsSoFar []interface{}) bool { return len(argsSoFar) < 3 }, func(argsSoFar []interface{}, arg interface{}) (skip bool, newNode interface{}) { // unconditionally process the argument, in case it needs to be skipped key, node := template.Walk(path, arg, templateRules) if skip, ok := key.(bool); ok && skip { return true, nil } if len(argsSoFar) == 2 { return false, arg // return unprocessed 3rd arg. It's a template. } return false, node }, ) if !ok { return key, node //passthru } var refNames []interface{} var refName interface{} if refNames, ok = args[0].([]interface{}); ok { if len(refNames) == 1 { refNames = []interface{}{nil, refNames[0]} } else if len(refNames) != 2 { return key, node //passthru } } else { refNames = []interface{}{nil, args[0]} } for _, refName = range refNames { if _, ok = refName.(string); !ok && refName != nil { return key, node //passthru } } valuesInterface := args[1] var values []interface{} if values, ok = valuesInterface.([]interface{}); !ok { return key, node //passthru } loopTemplate := interface{}(args[2]) generated := []interface{}{} for deepIndex, value := range values { refMap := make(map[string]interface{}) if refNames[0] != nil { refMap[refNames[0].(string)] = float64(deepIndex) } if refNames[1] != nil { refMap[refNames[1].(string)] = value } deepPath := make([]interface{}, len(path)+1) copy(deepPath, path) deepPath[cap(deepPath)-1] = interface{}(deepIndex) sources.Push(fallbackmap.DeepMap(refMap)) newIndex, processed := template.Walk(deepPath, loopTemplate, templateRules) sources.PopDiscard() if newIndex != nil { generated = append(generated, processed) } } return key, interface{}(generated) } }
func main() { templateRules := template.Rules{} inputParameters := NewInputsFlag(&templateRules) var templateFilename string var outputWhat OutputWhatFlag flag.StringVar(&templateFilename, "template", "-", "CloudFormation Template to process") flag.Var(&inputParameters, "parameters", "File to use of input parameters (can be specified multiple times)") flag.Var(&outputWhat, "output", "What to output after processing the Template") flag.Parse() var jsonStream io.Reader var err error if templateFilename == "-" { jsonStream = os.Stdin } else if jsonStream, err = os.Open(templateFilename); err != nil { panic(err) } dec := json.NewDecoder(jsonStream) t := make(map[string]interface{}) if err := dec.Decode(&t); err != nil { panic(err) } sources := fallbackmap.FallbackMap{} stack := deepstack.DeepStack{} sources.Attach(inputParameters.Get()) sources.Attach(deepalias.DeepAlias{&stack}) sources.Attach(deepcloudformationoutputs.NewDeepCloudFormationOutputs("eu-west-1")) sources.Attach(deepcloudformationresources.NewDeepCloudFormationResources("eu-west-1")) stack.Push(&sources) templateRules.AttachEarly(rules.ExcludeComments) templateRules.AttachEarly(rules.MakeFnFor(&stack, &templateRules)) templateRules.AttachEarly(rules.MakeFnWith(&stack, &templateRules)) templateRules.Attach(rules.FnAdd) templateRules.Attach(rules.FnIf) templateRules.Attach(rules.FnAnd) templateRules.Attach(rules.FnOr) templateRules.Attach(rules.FnNot) templateRules.Attach(rules.FnEquals) templateRules.Attach(rules.FnConcat) templateRules.Attach(rules.FnFromEntries) templateRules.Attach(rules.FnHasKey) templateRules.Attach(rules.FnJoin) templateRules.Attach(rules.FnKeys) templateRules.Attach(rules.FnLength) templateRules.Attach(rules.FnMerge) templateRules.Attach(rules.FnMergeDeep) templateRules.Attach(rules.FnMod) templateRules.Attach(rules.FnSplit) templateRules.Attach(rules.FnToEntries) templateRules.Attach(rules.FnUnique) templateRules.Attach(rules.MakeFnGetAtt(&stack, &templateRules)) templateRules.Attach(rules.MakeRef(&stack, &templateRules)) templateRules.Attach(rules.MakeFnHasRef(&stack)) templateRules.Attach(rules.MakeFnIncludeFile(vfs.OS("/"), &templateRules)) templateRules.Attach(rules.MakeFnIncludeFileRaw(vfs.OS("/"))) templateRules.Attach(rules.ReduceConditions) // First Pass (to collect Parameter names) processed := template.Process(t, &templateRules) parameterRefs := map[string]interface{}{} if processedMap, ok := processed.(map[string]interface{}); ok { if processedParameters, ok := processedMap["Parameters"]; ok { if processedParametersMap, ok := processedParameters.(map[string]interface{}); ok { for parameterName, _ := range processedParametersMap { parameterRefs[parameterName] = map[string]interface{}{ "ParamRef": parameterName, } } } } } stack.Push(fallbackmap.DeepMap(parameterRefs)) templateRules.Attach(func(path []interface{}, node interface{}) (interface{}, interface{}) { key := interface{}(nil) if len(path) > 0 { key = path[len(path)-1] } if nodeMap, ok := node.(map[string]interface{}); !ok || len(nodeMap) != 1 { return key, node //passthru } if refName, ok := node.(map[string]interface{})["ParamRef"]; ok { return key, interface{}(map[string]interface{}{"Ref": interface{}(refName)}) } return key, node }) processed = template.Process(t, &templateRules) stack.PopDiscard() switch outputWhat.Get().what { case OutputTemplate: enc := json.NewEncoder(os.Stdout) enc.Encode(processed) case OutputCredentials: credentials := []interface{}{} credentialMap := make(map[string]interface{}) for _, input := range inputParameters.Sources() { if !outputWhat.Get().hasKey || outputWhat.Get().key == input.filename { credentialMap = template.Process(input.data, &templateRules).(map[string]interface{}) credentialMap["$comment"] = map[string]interface{}{"filename": input.filename} credentials = append(credentials, credentialMap) } } if len(credentials) == 0 && outputWhat.Get().hasKey { panic(fmt.Errorf("No parameters file '%s' was input", outputWhat.Get().key)) } enc := json.NewEncoder(os.Stdout) if len(credentials) == 1 { enc.Encode(credentials[0]) } else { enc.Encode(credentials) } case OutputParameters: parameters := []cloudformation.Parameter{} for name, _ := range parameterRefs { value, ok := sources.Get([]string{name}) if !ok { continue } value = template.Process(value, &templateRules) parameters = append(parameters, func(name string, value interface{}) cloudformation.Parameter { stringval := fmt.Sprintf("%s", value) boolval := false return cloudformation.Parameter{ ParameterKey: &name, ParameterValue: &stringval, UsePreviousValue: &boolval, } }(name, value)) } enc := json.NewEncoder(os.Stdout) enc.Encode(parameters) } }