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  }