🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

github.com/buildkite/cli

Package Overview
Dependencies
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

github.com/buildkite/cli - go Package Compare versions

Comparing version
v0.3.0
to
v0.4.0
+39
CHANGELOG.md
# 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)
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.
+1
-0

@@ -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=

@@ -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 @@ })

@@ -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 @@

@@ -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 @@

@@ -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 @@ }

@@ -16,3 +16,4 @@ # bk - The Buildkite CLI

```
brew install buildkite/cli/bk
brew tap buildkite/cli
brew install bk
````

@@ -19,0 +20,0 @@

@@ -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 @@