github.com/spinnaker/spin@v1.30.0/cmd/pipeline-template/use.go (about) 1 // Copyright (c) 2018, Google, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pipeline_template 16 17 import ( 18 "errors" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "strings" 23 24 "github.com/spf13/cobra" 25 //"github.com/spinnaker/spin/cmd/output" 26 "sigs.k8s.io/yaml" 27 28 "github.com/spinnaker/spin/util" 29 ) 30 31 type useOptions struct { 32 *pipelineTemplateOptions 33 id string 34 tag string 35 application string 36 name string 37 description string 38 variables map[string]string 39 templateType string 40 artifactAccount string 41 variablesFiles []string 42 } 43 44 const ( 45 usePipelineTemplateShort = "Creates a pipeline configuration using a managed pipeline template" 46 usePipelineTemplateLong = "Creates a pipeline configuration using a managed pipeline template" 47 ) 48 49 func NewUseCmd(pipelineTemplateOptions *pipelineTemplateOptions) *cobra.Command { 50 options := &useOptions{ 51 pipelineTemplateOptions: pipelineTemplateOptions, 52 } 53 cmd := &cobra.Command{ 54 Use: "use", 55 Short: usePipelineTemplateShort, 56 Long: usePipelineTemplateLong, 57 RunE: func(cmd *cobra.Command, args []string) error { 58 return usePipelineTemplate(cmd, options, args) 59 }, 60 } 61 62 cmd.PersistentFlags().StringVar(&options.id, "id", "", "id of the pipeline template") 63 cmd.PersistentFlags().StringVarP(&options.application, "application", "a", "", "application to get the new pipeline") 64 cmd.PersistentFlags().StringVarP(&options.name, "name", "n", "", "name of the new pipeline") 65 cmd.PersistentFlags().StringVarP(&options.tag, "tag", "t", "", "(optional) specific tag to query") 66 cmd.PersistentFlags().StringVarP(&options.description, "description", "d", "", "(optional) description of the pipeline") 67 cmd.PersistentFlags().StringVar(&options.templateType, "type", "front50/pipelineTemplate", "(optional) template type") 68 cmd.PersistentFlags().StringVar(&options.artifactAccount, "artifact-account", "front50ArtifactCredentials", "(optional) artifact account") 69 cmd.PersistentFlags().StringToStringVar(&options.variables, "set", nil, "template variables/values required by the template. Format: key=val,key1=val1") 70 cmd.PersistentFlags().StringSliceVar(&options.variablesFiles, "values", nil, "json/yaml files with template variables and values") 71 72 return cmd 73 } 74 75 func usePipelineTemplate(cmd *cobra.Command, options *useOptions, args []string) error { 76 id, errID := getTemplateID(options, args) 77 if errID != nil { 78 return errID 79 } 80 81 // Check required params 82 options.application = strings.TrimSpace(options.application) 83 if options.application == "" { 84 return errors.New("no application name supplied, exiting") 85 } 86 options.name = strings.TrimSpace(options.name) 87 if options.name == "" { 88 return errors.New("no pipeline name supplied, exiting") 89 } 90 91 // Build pipeline using template, output 92 pipeline, err := buildUsingTemplate(id, options) 93 if err != nil { 94 return err 95 } 96 97 options.Ui.JsonOutput(pipeline) 98 99 return nil 100 } 101 102 func getTemplateID(options *useOptions, args []string) (string, error) { 103 // Check options if they passed in like --id 104 optionsID := strings.TrimSpace(options.id) 105 if optionsID != "" { 106 return optionsID, nil 107 } 108 // Otherwise get from arguments 109 argsID, err := util.ReadArgsOrStdin(args) 110 if err != nil { 111 return "", err 112 } 113 argsID = strings.TrimSpace(argsID) 114 if argsID == "" { 115 return "", errors.New("no pipeline template id supplied, exiting") 116 } 117 118 return argsID, nil 119 } 120 121 func buildUsingTemplate(id string, options *useOptions) (map[string]interface{}, error) { 122 pipeline := make(map[string]interface{}) 123 124 // get variables from cmd and files 125 variables, err := getVariables(options) 126 if err != nil { 127 return nil, err 128 } 129 130 // Configure pipeline.template 131 templateProperty := map[string]string{ 132 "artifactAccount": options.artifactAccount, 133 "type": options.templateType, 134 "reference": getFullTemplateID(id, options.tag), 135 } 136 137 // Configure pipeline 138 pipeline["template"] = templateProperty 139 pipeline["schema"] = "v2" 140 pipeline["application"] = options.application 141 pipeline["name"] = options.name 142 pipeline["description"] = options.description 143 pipeline["variables"] = variables 144 145 // Properties not supported by spin, add empty default values which can be populated manually if desired 146 pipeline["exclude"] = make([]string, 0) 147 pipeline["triggers"] = make([]string, 0) 148 pipeline["parameters"] = make([]string, 0) 149 pipeline["notifications"] = make([]string, 0) 150 pipeline["stages"] = make([]string, 0) 151 152 return pipeline, nil 153 } 154 155 func getVariables(options *useOptions) (map[string]string, error) { 156 // Create map for variables 157 var variables map[string]string 158 159 if len(options.variablesFiles) == 0 { 160 variables = make(map[string]string) 161 } else { 162 fileVars, err := parseKeyValsFromFile(options.variablesFiles, false) 163 if err != nil { 164 return nil, err 165 } 166 variables = fileVars 167 } 168 169 // Merge maps, with vars from command line overriding file vars 170 if len(options.variables) > 0 { 171 for k, v := range options.variables { 172 variables[k] = v 173 } 174 } 175 176 // return all variables from file and command line 177 return variables, nil 178 } 179 180 func getFullTemplateID(id string, tag string) string { 181 // If no protocol given, add default spinnaker:// 182 if !strings.Contains(id, "://") { 183 id = fmt.Sprintf("spinnaker://%s", id) 184 } 185 // Append the tag if they set one 186 if tag != "" { 187 id = fmt.Sprintf("%s:%s", id, tag) 188 } 189 // Otherwise they have set the protocol, return it back as is 190 return id 191 } 192 193 func parseKeyValsFromFile(filePaths []string, tolerateEmptyInput bool) (map[string]string, error) { 194 var fromFile *os.File 195 var err error 196 var variables map[string]string 197 198 for _, filePath := range filePaths { 199 if filePath == "" { 200 err = nil 201 if !tolerateEmptyInput { 202 err = errors.New("No file path given") 203 } 204 return nil, err 205 } 206 207 fromFile, err = os.Open(filePath) 208 if err != nil { 209 return nil, err 210 } 211 212 fi, err := fromFile.Stat() 213 if err != nil { 214 return nil, err 215 } 216 217 if fi.Size() <= 0 { 218 err = nil 219 if !tolerateEmptyInput { 220 err = errors.New("No json or yaml input to parse") 221 } 222 return nil, err 223 } 224 225 byteValue, err := ioutil.ReadAll(fromFile) 226 if err != nil { 227 return nil, fmt.Errorf("Failed to read file: %v", err) 228 } 229 230 err = yaml.UnmarshalStrict(byteValue, &variables) 231 if err != nil { 232 return nil, fmt.Errorf("Failed to unmarshal: %v", err) 233 } 234 } 235 236 return variables, nil 237 }