diff --git a/README.md b/README.md index a1232ff..6267c7a 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Then type path and name of new configuration file. To create new stack you have to type: -``~ $ perun create-stack ] `` + ### Configuration file You can find an example configuration file in the main directory of the repository in file `defaults/main.yml`. @@ -143,6 +144,14 @@ Example JSON template which describe S3 Bucket: If you want to destroy stack just type its name. Before you create stack you should validate it with perun :wink:. +### Capabilities + +If your template includes resources that can affect permissions in your AWS account, +you must explicitly acknowledge its capabilities by adding `--capabilities=CAPABILITY` flag. + +Valid values are `CAPABILITY_IAM` and `CAPABILITY_NAMED_IAM`. +You can specify both of them by adding `--capabilities=CAPABILITY_IAM --capabilities=CAPABILITY_NAMED_IAM`. + ## License [Apache License 2.0](LICENSE) @@ -152,12 +161,14 @@ Before you create stack you should validate it with perun :wink:. - [Piotr Figwer](https://github.com/pfigwer) - [Sylwia Gargula](https://github.com/SylwiaGargula) - [Wojciech Gawroński](https://github.com/afronski) -- [Jakub Lamparski](https://github.com/jlampar) +- [Mateusz Piwowarczyk](https://github.com/piwowarc) ## Contributors +- [Jakub Lamparski](https://github.com/jlampar) - [Aleksander Mamla](https://github.com/amamla) - [Kacper Patro](https://github.com/morfeush22) - [Paweł Pikuła](https://github.com/ppikula) - [Michał Połcik](https://github.com/mwpolcik) +- [Tomasz Raus](https://github.com/rusty-2) - [Maksymilian Wojczuk](https://github.com/maxiwoj) diff --git a/cliparser/cliparser.go b/cliparser/cliparser.go index 67d4499..6a2ac54 100644 --- a/cliparser/cliparser.go +++ b/cliparser/cliparser.go @@ -46,6 +46,7 @@ type CliArguments struct { Region *string Sandbox *bool Stack *string + Capabilities *[]string PrettyPrint *bool } @@ -77,13 +78,15 @@ func ParseCliArguments(args []string) (cliArguments CliArguments, err error) { configure = app.Command(ConfigureMode, "Create your own configuration mode") - createStack = app.Command(CreateStackMode, "Creates a stack on aws") - createStackName = createStack.Arg("stack", "An AWS stack name.").Required().String() - createStackTemplate = createStack.Arg("template", "A path to the template file.").Required().String() + createStack = app.Command(CreateStackMode, "Creates a stack on aws") + createStackName = createStack.Arg("stack", "An AWS stack name.").Required().String() + createStackTemplate = createStack.Arg("template", "A path to the template file.").Required().String() + createStackCapabilities = createStack.Flag("capabilities", "Capabilities: CAPABILITY_IAM | CAPABILITY_NAMED_IAM").Enums("CAPABILITY_IAM", "CAPABILITY_NAMED_IAM") deleteStack = app.Command(DestroyStackMode, "Deletes a stack on aws") deleteStackName = deleteStack.Arg("stack", "An AWS stack name.").Required().String() ) + app.HelpFlag.Short('h') app.Version(utilities.VersionStatus()) @@ -115,6 +118,7 @@ func ParseCliArguments(args []string) (cliArguments CliArguments, err error) { cliArguments.Mode = &CreateStackMode cliArguments.TemplatePath = createStackTemplate cliArguments.Stack = createStackName + cliArguments.Capabilities = createStackCapabilities // delete Stack case deleteStack.FullCommand(): diff --git a/converter/converter.go b/converter/converter.go index b8ba90a..339def8 100644 --- a/converter/converter.go +++ b/converter/converter.go @@ -42,7 +42,9 @@ func Convert(context *context.Context) error { // If input type file is JSON convert to YAML. if format == "JSON" { + outputTemplate, err = jsonToYaml(rawTemplate) + if err != nil { return err } @@ -55,6 +57,7 @@ func Convert(context *context.Context) error { context.Logger.Error(preprocessingError.Error()) } if *context.CliArguments.PrettyPrint == false { + outputTemplate, err = yamlToJson(preprocessed) } else if *context.CliArguments.PrettyPrint == true { outputTemplate, err = yamlToPrettyJson(preprocessed) @@ -116,6 +119,7 @@ func saveToFile(template []byte, path string, logger *logger.Logger) error { } func detectFormatFromContent(rawTemplate []byte) (format string) { + _, errorYAML := jsonToYaml(rawTemplate) _, errorJSON := yamlToJson(rawTemplate) diff --git a/logger/logger.go b/logger/logger.go index ae8b5b0..ad78edf 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -112,7 +112,6 @@ func (logger *Logger) GetInput(message string, v ...interface{}) error { } return nil } - func (logger *Logger) log(verbosity Verbosity, message string) { if !logger.Quiet && verbosity >= logger.Verbosity { fmt.Println(verbosity.String() + ": " + message) diff --git a/offlinevalidator/offlinevalidator.go b/offlinevalidator/offlinevalidator.go index fde480b..3ce383f 100644 --- a/offlinevalidator/offlinevalidator.go +++ b/offlinevalidator/offlinevalidator.go @@ -266,25 +266,25 @@ func obtainResources(goformationTemplate cloudformation.Template, perunTemplate for propertyName, propertyContent := range perunResources { if propertyContent.Properties == nil { - logger.Always("WARNING! " + propertyName + " <--- is nil.") + logger.Debug(propertyName + " <--- is nil.") } else { for element, elementValue := range propertyContent.Properties { if elementValue == nil { - logger.Always("WARNING! " + propertyName + ": " + element + " <--- is nil.") + logger.Debug(propertyName + ": " + element + " <--- is nil.") } else if elementMap, ok := elementValue.(map[string]interface{}); ok { for key, value := range elementMap { if value == nil { - logger.Always("WARNING! " + propertyName + ": " + element + ": " + key + " <--- is nil.") + logger.Debug(propertyName + ": " + element + ": " + key + " <--- is nil.") } else if elementOfElement, ok := value.(map[string]interface{}); ok { for subKey, subValue := range elementOfElement { if subValue == nil { - logger.Always("WARNING! " + propertyName + ": " + element + ": " + key + ": " + subKey + " <--- is nil.") + logger.Debug(propertyName + ": " + element + ": " + key + ": " + subKey + " <--- is nil.") } } } else if sliceOfElement, ok := value.([]interface{}); ok { for indexKey, indexValue := range sliceOfElement { if indexValue == nil { - logger.Always("WARNING! " + propertyName + ": " + element + ": " + key + "[" + strconv.Itoa(indexKey) + "] <--- is nil.") + logger.Debug(propertyName + ": " + element + ": " + key + "[" + strconv.Itoa(indexKey) + "] <--- is nil.") } } } @@ -292,17 +292,17 @@ func obtainResources(goformationTemplate cloudformation.Template, perunTemplate } else if elementSlice, ok := elementValue.([]interface{}); ok { for index, value := range elementSlice { if value == nil { - logger.Always("WARNING! " + propertyName + ": " + element + "[" + strconv.Itoa(index) + "] <--- is nil.") + logger.Debug(propertyName + ": " + element + "[" + strconv.Itoa(index) + "] <--- is nil.") } else if elementOfElement, ok := value.(map[string]interface{}); ok { for subKey, subValue := range elementOfElement { if subValue == nil { - logger.Always("WARNING! " + propertyName + ": " + element + "[" + strconv.Itoa(index) + "]: " + subKey + " <--- is nil.") + logger.Debug(propertyName + ": " + element + "[" + strconv.Itoa(index) + "]: " + subKey + " <--- is nil.") } } } else if sliceOfElement, ok := value.([]interface{}); ok { for indexKey, indexValue := range sliceOfElement { if indexValue == nil { - logger.Always("WARNING! " + propertyName + ": " + element + "[" + strconv.Itoa(index) + "][" + strconv.Itoa(indexKey) + "] <--- is nil.") + logger.Debug(propertyName + ": " + element + "[" + strconv.Itoa(index) + "][" + strconv.Itoa(indexKey) + "] <--- is nil.") } } } diff --git a/stack/stack.go b/stack/stack.go index 0f49f2c..f395e41 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -10,9 +10,17 @@ import ( // This function gets template and name of stack. It creates "CreateStackInput" structure. func createStackInput(context *context.Context, template *string, stackName *string) cloudformation.CreateStackInput { + + rawCapabilities := *context.CliArguments.Capabilities + capabilities := make([]*string, len(rawCapabilities)) + for i, capability := range rawCapabilities { + capabilities[i] = &capability + } + templateStruct := cloudformation.CreateStackInput{ TemplateBody: template, StackName: stackName, + Capabilities: capabilities, } return templateStruct } @@ -32,9 +40,10 @@ func getTemplateFromFile(context *context.Context) (string, string) { } // This function uses CreateStackInput variable to create Stack. -func createStack(templateStruct cloudformation.CreateStackInput, session *session.Session) { +func createStack(templateStruct cloudformation.CreateStackInput, session *session.Session) (err error) { api := cloudformation.New(session) - api.CreateStack(&templateStruct) + _, err = api.CreateStack(&templateStruct) + return } // This function uses all functions above and session to create Stack. @@ -49,7 +58,10 @@ func NewStack(context *context.Context) { if createSessionError != nil { context.Logger.Error(createSessionError.Error()) } - createStack(templateStruct, session) + createStackError := createStack(templateStruct, session) + if createStackError != nil { + context.Logger.Error(createStackError.Error()) + } } // This function bases on "DeleteStackInput" structure and destroys stack. It uses "StackName" to choose which stack will be destroy. Before that it creates session.