github.com/buildkite/cli
Advanced tools
+39
| # Changelog | ||
| All notable changes to this project will be documented in this file. | ||
| The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) | ||
| and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | ||
| ## [v0.4.0](https://github.com/buildkite/cli/tree/v0.4.0) (2019-02-17) | ||
| [Full Changelog](https://github.com/buildkite/cli/compare/v0.3.0...v0.4.0) | ||
| ### Changed | ||
| - Implement text and select block steps [#25](https://github.com/buildkite/cli/pull/25) (@lox) | ||
| - Add MIT license [#34](https://github.com/buildkite/cli/pull/34) (@lox) | ||
| - Add plugins-path and switch to temp dirs [#30](https://github.com/buildkite/cli/pull/30) (@toolmantim) | ||
| - Add some more tests and fixes for pipeline parsing [#24](https://github.com/buildkite/cli/pull/24) (@lox) | ||
| ## [v0.3.0](https://github.com/buildkite/cli/tree/v0.3.0) (2019-02-04) | ||
| [Full Changelog](https://github.com/buildkite/cli/compare/v0.2.0...v0.3.0) | ||
| ### Changed | ||
| - Expose more of the keyring options [#22](https://github.com/buildkite/cli/pull/22) (@lox) | ||
| - Add a run alias for local run [#21](https://github.com/buildkite/cli/pull/21) (@lox) | ||
| - Provide an empty builds dir and close the bootstrap script [#20](https://github.com/buildkite/cli/pull/20) (@lox) | ||
| - Rename commands [#13](https://github.com/buildkite/cli/pull/13) (@lox) | ||
| - Reduce polling intervals in local pipeline processor [#10](https://github.com/buildkite/cli/pull/10) (@lox) | ||
| ## [v0.2.0](https://github.com/buildkite/cli/tree/v0.2.0) (2018-10-31) | ||
| [Full Changelog](https://github.com/buildkite/cli/compare/v0.1.0...v0.2.0) | ||
| ### Changed | ||
| - Add a Homebrew tap release process [#7](https://github.com/buildkite/cli/pull/7) (@toolmantim) | ||
| - Show the correct path for config file. [#9](https://github.com/buildkite/cli/pull/9) (@lox) | ||
| - Readme cleanups [#8](https://github.com/buildkite/cli/pull/8) (@toolmantim) | ||
| ## [v0.1.0](https://github.com/buildkite/cli/tree/v0.1.0) (2018-10-18) | ||
| [Full Changelog](https://github.com/buildkite/cli/compare/2a544ab29355...v0.1.0) | ||
| ### Changed | ||
| - Local run command [#6](https://github.com/buildkite/cli/pull/6) (@lox) | ||
| - Update to golang 1.11 and modules [#5](https://github.com/buildkite/cli/pull/5) (@lox) |
+21
| MIT License | ||
| Copyright (c) 2014-2019 Buildkite Pty Ltd | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
@@ -24,1 +24,2 @@ # Config for https://github.com/apps/tap-release | ||
| end | ||
+1
-0
@@ -18,2 +18,3 @@ module github.com/buildkite/cli | ||
| github.com/fatih/color v1.7.0 | ||
| github.com/go-test/deep v1.0.1 | ||
| github.com/godbus/dbus v4.1.0+incompatible // indirect | ||
@@ -20,0 +21,0 @@ github.com/golang/protobuf v1.1.0 // indirect |
+2
-2
@@ -29,2 +29,4 @@ github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f h1:WXiWWJrYCaOaYimBAXlRdRJ7qOisrYyMLYnCvvhHVms= | ||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||
| github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= | ||
| github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||
| github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= | ||
@@ -74,4 +76,2 @@ github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= | ||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
| golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo= | ||
| golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
| golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= | ||
@@ -78,0 +78,0 @@ golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
+252
-23
@@ -5,6 +5,116 @@ package local | ||
| "encoding/json" | ||
| "reflect" | ||
| "testing" | ||
| "github.com/go-test/deep" | ||
| ) | ||
| func TestParsingStringable(t *testing.T) { | ||
| for _, tc := range []struct { | ||
| JSON string | ||
| Expected string | ||
| }{ | ||
| {`true`, `true`}, | ||
| {`false`, `false`}, | ||
| {`"true"`, `true`}, | ||
| {`"true"`, `true`}, | ||
| {`1`, `1`}, | ||
| {`"1"`, `1`}, | ||
| {`1.23`, `1.23`}, | ||
| } { | ||
| t.Run("", func(t *testing.T) { | ||
| var target stringable | ||
| if err := json.Unmarshal([]byte(tc.JSON), &target); err != nil { | ||
| t.Fatalf("failed to parse json: %v", err) | ||
| } | ||
| if diff := deep.Equal(tc.Expected, string(target)); diff != nil { | ||
| t.Error(diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestParsingEnvMapOrSlice(t *testing.T) { | ||
| for _, tc := range []struct { | ||
| JSON string | ||
| Expected []string | ||
| }{ | ||
| { | ||
| `{ "FOO": false, "BAR": "foo", "BAZ": 12 }`, | ||
| []string{"BAR=foo", "BAZ=12", "FOO=false"}, | ||
| }, | ||
| { | ||
| `[ "BAR=foo", "BAZ=12", "FOO=false" ]`, | ||
| []string{"BAR=foo", "BAZ=12", "FOO=false"}, | ||
| }, | ||
| } { | ||
| t.Run("", func(t *testing.T) { | ||
| var target envMapOrSlice | ||
| if err := json.Unmarshal([]byte(tc.JSON), &target); err != nil { | ||
| t.Fatalf("failed to parse json: %v", err) | ||
| } | ||
| if diff := deep.Equal(tc.Expected, []string(target)); diff != nil { | ||
| t.Error(diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestStepParsingCommandSteps(t *testing.T) { | ||
| for _, tc := range []struct { | ||
| JSON string | ||
| Expected step | ||
| }{ | ||
| { | ||
| `{ | ||
| "label": "testing with env as a map", | ||
| "command": "echo hi $$FOO", | ||
| "env": { | ||
| "FOO": false, | ||
| "BAR": "foo", | ||
| "BAZ": 12 | ||
| } | ||
| }`, | ||
| step{ | ||
| Command: &commandStep{ | ||
| Label: `testing with env as a map`, | ||
| Commands: []string{`echo hi $$FOO`}, | ||
| Env: envMapOrSlice{ | ||
| "BAR=foo", | ||
| "BAZ=12", | ||
| "FOO=false", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| `{ | ||
| "label": "testing with boolean commands", | ||
| "command": [ "echo llamas", false ] | ||
| }`, | ||
| step{ | ||
| Command: &commandStep{ | ||
| Label: `testing with boolean commands`, | ||
| Commands: []string{`echo llamas`, `false`}, | ||
| }, | ||
| }, | ||
| }, | ||
| } { | ||
| t.Run("", func(t *testing.T) { | ||
| var step step | ||
| if err := json.Unmarshal([]byte(tc.JSON), &step); err != nil { | ||
| t.Fatalf("failed to parse json: %v", err) | ||
| } | ||
| if diff := deep.Equal(tc.Expected, step); diff != nil { | ||
| t.Error(diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestPipelineParsing(t *testing.T) { | ||
@@ -15,32 +125,151 @@ for _, tc := range []struct { | ||
| }{ | ||
| {`{"steps":[{"command": "echo hello world"}]}`, pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Commands: []string{"echo hello world"}, | ||
| { | ||
| `{ | ||
| "steps": [ | ||
| { | ||
| "command": "echo hello world" | ||
| } | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Commands: []string{"echo hello world"}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }}, | ||
| {`{"steps":[{"commands": ["echo hello world","pwd"]}]}`, pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Commands: []string{"echo hello world", "pwd"}, | ||
| }, | ||
| { | ||
| `{ | ||
| "steps":[ | ||
| { | ||
| "commands": [ "echo hello world", "pwd" ] | ||
| } | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Commands: []string{"echo hello world", "pwd"}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }}, | ||
| {`{"steps":[{"wait":""},{"label": "llamas"}]}`, pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Wait: &waitStep{}, | ||
| }, | ||
| { | ||
| `{ | ||
| "steps": [ | ||
| {"wait": ""}, | ||
| {"label": "llamas"} | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Wait: &waitStep{}, | ||
| }, | ||
| { | ||
| Command: &commandStep{ | ||
| Label: "llamas", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Command: &commandStep{ | ||
| Label: "llamas", | ||
| }, | ||
| }, | ||
| { | ||
| `{ | ||
| "steps": [ | ||
| { | ||
| "commands": [ | ||
| "echo hello world", | ||
| "pwd" | ||
| ], | ||
| "plugins": { | ||
| "blah-blah/blah#v0.0.1": null, | ||
| "blah-blah/another#v0.0.1": { | ||
| "my_config":"totes" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Commands: []string{"echo hello world", "pwd"}, | ||
| Plugins: []Plugin{ | ||
| {Name: "blah-blah/blah#v0.0.1"}, | ||
| {Name: "blah-blah/another#v0.0.1", Params: map[string]interface{}{ | ||
| "my_config": "totes", | ||
| }}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }}, | ||
| }, | ||
| { | ||
| `{ | ||
| "steps": [ | ||
| { | ||
| "plugins": [ | ||
| "blah-blah/blah#v0.0.1" | ||
| ] | ||
| } | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Command: &commandStep{ | ||
| Plugins: []Plugin{ | ||
| {Name: "blah-blah/blah#v0.0.1"}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| `{ | ||
| "steps": [ | ||
| {"wait": ""}, | ||
| {"label": "llamas"} | ||
| ] | ||
| }`, | ||
| pipeline{ | ||
| Steps: []step{ | ||
| { | ||
| Wait: &waitStep{}, | ||
| }, | ||
| { | ||
| Command: &commandStep{ | ||
| Label: "llamas", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| `{ | ||
| "env": { | ||
| "FOO": false, | ||
| "BAR": "foo", | ||
| "BAZ": 12 | ||
| }, | ||
| "steps": [] | ||
| }`, | ||
| pipeline{ | ||
| Env: envMapOrSlice{ | ||
| "BAR=foo", | ||
| "BAZ=12", | ||
| "FOO=false", | ||
| }, | ||
| Steps: []step{}, | ||
| }, | ||
| }, | ||
| } { | ||
@@ -54,4 +283,4 @@ t.Run("", func(t *testing.T) { | ||
| if !reflect.DeepEqual(tc.Expected, pipeline) { | ||
| t.Fatalf("Expected %v, got %v", tc.Expected, pipeline) | ||
| if diff := deep.Equal(tc.Expected, pipeline); diff != nil { | ||
| t.Error(diff) | ||
| } | ||
@@ -58,0 +287,0 @@ }) |
+82
-18
@@ -7,2 +7,3 @@ package local | ||
| "log" | ||
| "sort" | ||
| "strings" | ||
@@ -112,4 +113,20 @@ ) | ||
| type blockField struct { | ||
| Text string `json:"text"` | ||
| Select string `json:"select"` | ||
| Key string `json:"key"` | ||
| Hint string `json:"hint"` | ||
| Required bool `json:"required"` | ||
| Default string `json:"default"` | ||
| Options []blockSelectOption `json:"options"` | ||
| } | ||
| type blockSelectOption struct { | ||
| Label string `json:"label"` | ||
| Value string `json:"value"` | ||
| } | ||
| type blockStep struct { | ||
| Block string `json:"block"` | ||
| Block string `json:"block"` | ||
| Fields []blockField `json:"fields"` | ||
| } | ||
@@ -148,3 +165,3 @@ | ||
| if err := json.Unmarshal(data, &intermediate); err != nil { | ||
| return err | ||
| return fmt.Errorf("invalid command step: %v", err) | ||
| } | ||
@@ -167,6 +184,2 @@ | ||
| var pluginSlice struct { | ||
| Plugins []map[string]interface{} `json:"plugins"` | ||
| } | ||
| // Normalize env vs environment | ||
@@ -178,9 +191,19 @@ s.Env = []string(intermediate.Env) | ||
| s.Plugins = nil | ||
| var pluginSlice struct { | ||
| Plugins []map[string]interface{} `json:"plugins"` | ||
| } | ||
| if err := json.Unmarshal(data, &pluginSlice); err == nil { | ||
| for _, p := range pluginSlice.Plugins { | ||
| for k, v := range p { | ||
| s.Plugins = append(s.Plugins, Plugin{ | ||
| Name: k, | ||
| Params: v.(map[string]interface{}), | ||
| }) | ||
| switch vv := v.(type) { | ||
| case map[string]interface{}: | ||
| s.Plugins = append(s.Plugins, Plugin{Name: k, Params: vv}) | ||
| case nil: | ||
| s.Plugins = append(s.Plugins, Plugin{Name: k}) | ||
| default: | ||
| return fmt.Errorf("Unknown plugin value type %T", v) | ||
| } | ||
| } | ||
@@ -190,2 +213,14 @@ } | ||
| var pluginStringSlice struct { | ||
| Plugins []string `json:"plugins"` | ||
| } | ||
| if err := json.Unmarshal(data, &pluginStringSlice); err == nil { | ||
| for _, p := range pluginStringSlice.Plugins { | ||
| s.Plugins = append(s.Plugins, Plugin{ | ||
| Name: p, | ||
| }) | ||
| } | ||
| } | ||
| var pluginMap struct { | ||
@@ -213,4 +248,4 @@ Plugins map[string]interface{} `json:"plugins"` | ||
| type pipeline struct { | ||
| Steps []step `json:"steps"` | ||
| Env map[string]interface{} `json:"env"` | ||
| Steps []step `json:"steps"` | ||
| Env envMapOrSlice `json:"env"` | ||
| } | ||
@@ -229,13 +264,33 @@ | ||
| type stringable string | ||
| func (s *stringable) UnmarshalJSON(data []byte) error { | ||
| var target interface{} | ||
| if err := json.Unmarshal(data, &target); err != nil { | ||
| return err | ||
| } | ||
| switch target.(type) { | ||
| case string, int, int64, bool, float32, float64: | ||
| *s = stringable(fmt.Sprintf("%v", target)) | ||
| default: | ||
| return fmt.Errorf("Unstringable type of %T", target) | ||
| } | ||
| return nil | ||
| } | ||
| type stringOrSlice []string | ||
| func (s *stringOrSlice) UnmarshalJSON(data []byte) error { | ||
| var str string | ||
| var str stringable | ||
| *s = []string{} | ||
| if err := json.Unmarshal(data, &str); err == nil { | ||
| *s = []string{str} | ||
| *s = []string{string(str)} | ||
| return nil | ||
| } | ||
| var strSlice []string | ||
| var strSlice []stringable | ||
@@ -246,3 +301,6 @@ if err := json.Unmarshal(data, &strSlice); err != nil { | ||
| *s = strSlice | ||
| for _, str := range strSlice { | ||
| *s = append(*s, string(str)) | ||
| } | ||
| return nil | ||
@@ -254,3 +312,4 @@ } | ||
| func (s *envMapOrSlice) UnmarshalJSON(data []byte) error { | ||
| var m map[string]string | ||
| var m map[string]stringable | ||
| *s = []string{} | ||
@@ -261,2 +320,7 @@ if err := json.Unmarshal(data, &m); err == nil { | ||
| } | ||
| // maps are unordered, this makes them predictable | ||
| sorted := sort.StringSlice(*s) | ||
| sorted.Sort() | ||
| return nil | ||
@@ -268,3 +332,3 @@ } | ||
| if err := json.Unmarshal(data, &envSlice); err != nil { | ||
| return err | ||
| return fmt.Errorf("env must be a slice or a map: %v", err) | ||
| } | ||
@@ -271,0 +335,0 @@ |
+105
-7
@@ -5,2 +5,3 @@ package local | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
@@ -147,7 +148,8 @@ "io" | ||
| dryRunNote := "" | ||
| if params.DryRun { | ||
| dryRunNote = " (dry-run)" | ||
| } | ||
| if step.Command != nil { | ||
| dryRunNote := "" | ||
| if params.DryRun { | ||
| dryRunNote = " (dry-run)" | ||
| } | ||
| headerColor.Printf(">>> Executing command step %s%s\n", | ||
@@ -179,2 +181,88 @@ ejl.Render(step.Command.Label), | ||
| } else if step.Block != nil { | ||
| headerColor.Printf(">>> Blocking on %q%s\n", step.Block.Block, dryRunNote) | ||
| if !params.DryRun { | ||
| for _, field := range step.Block.Fields { | ||
| switch { | ||
| case field.Text != "": | ||
| fmt.Println() | ||
| prompt := promptui.Prompt{ | ||
| Label: field.Text, | ||
| Default: field.Default, | ||
| AllowEdit: true, | ||
| } | ||
| if field.Required { | ||
| prompt.Label = fmt.Sprintf("%s%s", prompt.Label, " (required)") | ||
| prompt.Validate = func(input string) error { | ||
| if input == "" { | ||
| return errors.New("Value required") | ||
| } | ||
| return nil | ||
| } | ||
| } | ||
| result, err := prompt.Run() | ||
| if err != nil { | ||
| fmt.Printf("Prompt failed %v\n", err) | ||
| } | ||
| server.SetMetadata(field.Key, result) | ||
| case field.Select != "": | ||
| fmt.Println() | ||
| templates := &promptui.SelectTemplates{ | ||
| Inactive: " {{ .Label | cyan }}", | ||
| Active: fmt.Sprintf("%s {{ .Label | underline }}", promptui.IconSelect), | ||
| } | ||
| items := field.Options | ||
| prompt := promptui.Select{ | ||
| Label: field.Select, | ||
| Items: items, | ||
| Templates: templates, | ||
| } | ||
| if field.Required { | ||
| prompt.Label = fmt.Sprintf("%s%s", prompt.Label, " (required)") | ||
| } else { | ||
| items = append([]blockSelectOption{{Label: "Empty"}}, field.Options...) | ||
| prompt.Items = items | ||
| } | ||
| i, _, err := prompt.Run() | ||
| if err != nil { | ||
| fmt.Printf("Prompt failed %v\n", err) | ||
| return err | ||
| } | ||
| server.SetMetadata(field.Key, items[i].Value) | ||
| } | ||
| } | ||
| fmt.Println() | ||
| prompt := promptui.Prompt{ | ||
| Label: fmt.Sprintf("Unblock %q", ejl.Render(step.Block.Block)), | ||
| IsConfirm: true, | ||
| Default: "y", | ||
| } | ||
| result, err := prompt.Run() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Println() | ||
| if result == "n" { | ||
| return fmt.Errorf("Unblock failed") | ||
| } | ||
| } | ||
| continue | ||
| } else { | ||
@@ -295,3 +383,3 @@ return fmt.Errorf("Unknown step type: %s", step) | ||
| buildDir, err := ioutil.TempFile(os.TempDir(), "buildkite-build-") | ||
| buildDir, err := ioutil.TempDir(os.TempDir(), "buildkite-build-") | ||
| if err != nil { | ||
@@ -301,2 +389,11 @@ return err | ||
| defer os.RemoveAll(buildDir) | ||
| pluginsDir, err := ioutil.TempDir(os.TempDir(), "buildkite-plugins-") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer os.RemoveAll(pluginsDir) | ||
| cmd.Env = append(a.Env, | ||
@@ -309,3 +406,4 @@ `HOME=`+os.Getenv(`HOME`), | ||
| `BUILDKITE_BOOTSTRAP_SCRIPT_PATH=`+bootstrap.Name(), | ||
| `BUILDKITE_BUILD_PATH=`+buildDir.Name(), | ||
| `BUILDKITE_BUILD_PATH=`+buildDir, | ||
| `BUILDKITE_PLUGINS_PATH=`+pluginsDir, | ||
| ) | ||
@@ -378,3 +476,3 @@ | ||
| step | ||
| env map[string]interface{} | ||
| env []string | ||
| } | ||
@@ -381,0 +479,0 @@ |
+6
-2
@@ -508,2 +508,6 @@ package local | ||
| func (a *apiServer) SetMetadata(k, v string) { | ||
| a.metadata.Store(k, v) | ||
| } | ||
| func (a *apiServer) handleMetadataSet(w http.ResponseWriter, r *http.Request, jobID string) { | ||
@@ -602,4 +606,4 @@ w.Header().Set("Content-Type", "application/json") | ||
| if err := readRequestInto(r, &pur); err != nil { | ||
| log.Printf("Failed to parse pipeline: %v", err) | ||
| http.Error(w, err.Error(), http.StatusInternalServerError) | ||
| log.Printf("Failed to parse pipeline upload: %v", err) | ||
| http.Error(w, err.Error(), 422) | ||
| return | ||
@@ -606,0 +610,0 @@ } |
+2
-1
@@ -16,3 +16,4 @@ # bk - The Buildkite CLI | ||
| ``` | ||
| brew install buildkite/cli/bk | ||
| brew tap buildkite/cli | ||
| brew install bk | ||
| ```` | ||
@@ -19,0 +20,0 @@ |
+1
-1
@@ -5,3 +5,3 @@ package cli | ||
| // Version is the version of the CLI tool | ||
| Version = "0.3.0" | ||
| Version = "0.4.0" | ||
| ) | ||
@@ -8,0 +8,0 @@ |