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 }