github.com/discordapp/buildkite-agent@v2.6.6+incompatible/clicommand/pipeline_upload.go (about)

     1  package clicommand
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/andrew-d/go-termutil"
    12  	"github.com/buildkite/agent/agent"
    13  	"github.com/buildkite/agent/api"
    14  	"github.com/buildkite/agent/cliconfig"
    15  	"github.com/buildkite/agent/logger"
    16  	"github.com/buildkite/agent/retry"
    17  	"github.com/codegangsta/cli"
    18  )
    19  
    20  var PipelineUploadHelpDescription = `Usage:
    21  
    22     buildkite-agent pipeline upload <file> [arguments...]
    23  
    24  Description:
    25  
    26     Allows you to change the pipeline of a running build by uploading either a
    27     JSON or Yaml configuration file. If no configuration file is provided,
    28     we look for the file in the following locations:
    29  
    30     - buildkite.yml
    31     - buildkite.json
    32     - .buildkite/pipeline.yml
    33     - .buildkite/pipeline.json
    34     - .buildkite/steps.json (deprecated, removed in v2.2)
    35  
    36     You can also pipe build pipelines to the command, allowing you to create scripts
    37     that generate dynamic pipelines.
    38  
    39  Example:
    40  
    41     $ buildkite-agent pipeline upload
    42     $ buildkite-agent pipeline upload my-custom-steps.json
    43     $ ./script/dynamic_step_generator | buildkite-agent pipeline upload`
    44  
    45  type PipelineUploadConfig struct {
    46  	FilePath         string `cli:"arg:0" label:"upload paths"`
    47  	Replace          bool   `cli:"replace"`
    48  	Job              string `cli:"job" validate:"required"`
    49  	AgentAccessToken string `cli:"agent-access-token" validate:"required"`
    50  	Endpoint         string `cli:"endpoint" validate:"required"`
    51  	NoColor          bool   `cli:"no-color"`
    52  	Debug            bool   `cli:"debug"`
    53  	DebugHTTP        bool   `cli:"debug-http"`
    54  }
    55  
    56  var PipelineUploadCommand = cli.Command{
    57  	Name:        "upload",
    58  	Usage:       "Uploads a description of a build pipleine adds it to the currently running build after the current job.",
    59  	Description: PipelineUploadHelpDescription,
    60  	Flags: []cli.Flag{
    61  		cli.BoolFlag{
    62  			Name:   "replace",
    63  			Usage:  "Replace the rest of the existing pipeline with the steps uploaded. Jobs that are already running are not removed.",
    64  			EnvVar: "BUILDKITE_PIPELINE_REPLACE",
    65  		},
    66  		cli.StringFlag{
    67  			Name:   "job",
    68  			Value:  "",
    69  			Usage:  "The job that is making the changes to it's build",
    70  			EnvVar: "BUILDKITE_JOB_ID",
    71  		},
    72  		AgentAccessTokenFlag,
    73  		EndpointFlag,
    74  		NoColorFlag,
    75  		DebugFlag,
    76  		DebugHTTPFlag,
    77  	},
    78  	Action: func(c *cli.Context) {
    79  		// The configuration will be loaded into this struct
    80  		cfg := PipelineUploadConfig{}
    81  
    82  		// Load the configuration
    83  		loader := cliconfig.Loader{CLI: c, Config: &cfg}
    84  		if err := loader.Load(); err != nil {
    85  			logger.Fatal("%s", err)
    86  		}
    87  
    88  		// Setup the any global configuration options
    89  		HandleGlobalFlags(cfg)
    90  
    91  		// Find the pipeline file either from STDIN or the first
    92  		// argument
    93  		var input []byte
    94  		var err error
    95  		var filename string
    96  
    97  		if cfg.FilePath != "" {
    98  			filename = filepath.Base(cfg.FilePath)
    99  			input, err = ioutil.ReadFile(cfg.FilePath)
   100  			if err != nil {
   101  				logger.Fatal("Failed to read file: %s", err)
   102  			}
   103  		} else if !termutil.Isatty(os.Stdin.Fd()) {
   104  			input, err = ioutil.ReadAll(os.Stdin)
   105  			if err != nil {
   106  				logger.Fatal("Failed to read from STDIN: %s", err)
   107  			}
   108  		} else {
   109  			paths := []string{
   110  				"buildkite.yml",
   111  				"buildkite.json",
   112  				".buildkite/pipeline.yml",
   113  				".buildkite/pipeline.json",
   114  				".buildkite/steps.json",
   115  			}
   116  
   117  			// Collect all the files that exist
   118  			exists := []string{}
   119  			for _, path := range paths {
   120  				if _, err := os.Stat(path); err == nil {
   121  					exists = append(exists, path)
   122  				}
   123  			}
   124  
   125  			// If more than 1 of the config files exist, throw an
   126  			// error. There can only be one!!
   127  			if len(exists) > 1 {
   128  				logger.Fatal("Found multiple configuration files: %s. Please only have 1 configuration file present.", strings.Join(exists, ", "))
   129  			} else if len(exists) == 0 {
   130  				logger.Fatal("Could not find a default pipeline configuration file. See `buildkite-agent pipeline upload --help` for more information.")
   131  			}
   132  
   133  			found := exists[0]
   134  
   135  			// Warn about the deprecated steps.json
   136  			if found == ".buildkite/steps.json" {
   137  				logger.Warn("The default steps.json file has been deprecated and will be removed in v2.2. Please rename to .buildkite/pipeline.json and wrap the steps array in a `steps` property: { \"steps\": [ ... ] } }")
   138  			}
   139  
   140  			// Read the default file
   141  			filename = path.Base(found)
   142  			input, err = ioutil.ReadFile(found)
   143  			if err != nil {
   144  				logger.Fatal("Failed to read file %s: %s", found, err)
   145  			}
   146  		}
   147  
   148  		// Create the API client
   149  		client := agent.APIClient{
   150  			Endpoint: cfg.Endpoint,
   151  			Token:    cfg.AgentAccessToken,
   152  		}.Create()
   153  
   154  		// Generate a UUID that will identifiy this pipeline change. We
   155  		// do this outside of the retry loop because we want this UUID
   156  		// to be the same for each attempt at updating the pipeline.
   157  		uuid := api.NewUUID()
   158  
   159  		// Retry the pipeline upload a few times before giving up
   160  		err = retry.Do(func(s *retry.Stats) error {
   161  			_, err = client.Pipelines.Upload(cfg.Job, &api.Pipeline{UUID: uuid, Data: input, FileName: filename, Replace: cfg.Replace})
   162  			if err != nil {
   163  				logger.Warn("%s (%s)", err, s)
   164  			}
   165  
   166  			return err
   167  		}, &retry.Config{Maximum: 5, Interval: 1 * time.Second})
   168  		if err != nil {
   169  			logger.Fatal("Failed to upload and process pipeline: %s", err)
   170  		}
   171  
   172  		logger.Info("Successfully uploaded and parsed pipeline config")
   173  	},
   174  }