github.com/buildkite/cli
Advanced tools
| package cli | ||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "os" | ||
| "path/filepath" | ||
| "github.com/buildkite/cli/graphql" | ||
| zglob "github.com/mattn/go-zglob" | ||
| ) | ||
| type ArtifactDownloadCommandContext struct { | ||
| TerminalContext | ||
| KeyringContext | ||
| Debug bool | ||
| DebugHTTP bool | ||
| Build string | ||
| Job string | ||
| Pattern string | ||
| } | ||
| func ArtifactDownloadCommand(ctx ArtifactDownloadCommandContext) error { | ||
| bk, err := ctx.BuildkiteGraphQLClient() | ||
| if err != nil { | ||
| return NewExitError(err, 1) | ||
| } | ||
| if ctx.Job == "" && ctx.Build == "" { | ||
| return NewExitError(errors.New("--job or --build required"), 1) | ||
| } | ||
| try := ctx.Try() | ||
| try.Start("Downloading artifacts") | ||
| var artifacts []artifact | ||
| if ctx.Build != "" { | ||
| artifacts, err = findBuildkiteBuildArtifacts(bk, ctx.Build) | ||
| if err != nil { | ||
| try.Failure("Failed") | ||
| return NewExitError(err, 1) | ||
| } | ||
| } else if ctx.Job != "" { | ||
| artifacts, err = findBuildkiteJobArtifacts(bk, ctx.Job) | ||
| if err != nil { | ||
| try.Failure("Failed") | ||
| return NewExitError(err, 1) | ||
| } | ||
| } | ||
| if ctx.Pattern != "" { | ||
| glob, err := zglob.New(ctx.Pattern) | ||
| if err != nil { | ||
| try.Failure("Failed") | ||
| return NewExitError(err, 1) | ||
| } | ||
| var matchingArtifacts []artifact | ||
| for _, artifact := range artifacts { | ||
| if glob.Match(artifact.Path) { | ||
| matchingArtifacts = append(matchingArtifacts, artifact) | ||
| } | ||
| } | ||
| artifacts = matchingArtifacts | ||
| } | ||
| total := len(artifacts) | ||
| for _, artifact := range artifacts { | ||
| err := downloadArtifact(artifact) | ||
| if err != nil { | ||
| try.Failure("Failed") | ||
| return NewExitError(err, 1) | ||
| } | ||
| try.Println(fmt.Sprintf("%s (%d bytes)", artifact.Path, artifact.Size)) | ||
| } | ||
| try.Success(fmt.Sprintf("Downloaded %d artifacts", total)) | ||
| return nil | ||
| } | ||
| const artifactDownloadLimit = 500 | ||
| type artifact struct { | ||
| ID string | ||
| Path string | ||
| Size int | ||
| DownloadURL string | ||
| } | ||
| func findBuildkiteJobArtifacts(client *graphql.Client, jobID string) ([]artifact, error) { | ||
| resp, err := client.Do(` | ||
| query($jobID: ID!, $limit: Int!) { | ||
| job(uuid: $jobID) { | ||
| ...on JobTypeCommand { | ||
| artifacts(first: $limit) { | ||
| count | ||
| edges { | ||
| node { | ||
| id | ||
| path | ||
| size | ||
| downloadURL | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, map[string]interface{}{ | ||
| "jobID": jobID, | ||
| "limit": artifactDownloadLimit, | ||
| }) | ||
| if err != nil { | ||
| return []artifact{}, fmt.Errorf("Failed perform GraphQL query: %v", err) | ||
| } | ||
| var parsedResp struct { | ||
| Data struct { | ||
| Job struct { | ||
| Artifacts struct { | ||
| Count int `json:"count"` | ||
| Edges []struct { | ||
| Node struct { | ||
| ID string `json:"id"` | ||
| Path string `json:"path"` | ||
| Size int `json:"size"` | ||
| DownloadURL string `json:"downloadURL"` | ||
| } `json:"node"` | ||
| } `json:"edges"` | ||
| } `json:"artifacts"` | ||
| } `json:"job"` | ||
| } `json:"data"` | ||
| } | ||
| if err = resp.DecodeInto(&parsedResp); err != nil { | ||
| return []artifact{}, fmt.Errorf("Failed to parse GraphQL response: %v", err) | ||
| } | ||
| if parsedResp.Data.Job.Artifacts.Count > artifactDownloadLimit { | ||
| // GraphQL CommandJob.artifacts only supports `first` so cannot paginate | ||
| return []artifact{}, fmt.Errorf("Too many artifacts\n\nJob has %d artifacts but this tool can only download %d", | ||
| parsedResp.Data.Job.Artifacts.Count, artifactDownloadLimit) | ||
| } | ||
| var artifacts []artifact | ||
| for _, artifactEdge := range parsedResp.Data.Job.Artifacts.Edges { | ||
| artifacts = append(artifacts, artifact{ | ||
| ID: artifactEdge.Node.ID, | ||
| Path: artifactEdge.Node.Path, | ||
| Size: artifactEdge.Node.Size, | ||
| DownloadURL: artifactEdge.Node.DownloadURL, | ||
| }) | ||
| } | ||
| return artifacts, nil | ||
| } | ||
| func findBuildkiteBuildArtifacts(client *graphql.Client, buildID string) ([]artifact, error) { | ||
| resp, err := client.Do(` | ||
| query($buildID: ID!, $limit: Int!) { | ||
| build(uuid: $buildID) { | ||
| jobs(first: $limit) { | ||
| count | ||
| edges { | ||
| node { | ||
| ...on JobTypeCommand { | ||
| artifacts(first: $limit) { | ||
| count | ||
| edges { | ||
| node { | ||
| id | ||
| path | ||
| size | ||
| downloadURL | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, map[string]interface{}{ | ||
| "buildID": buildID, | ||
| "limit": artifactDownloadLimit, | ||
| }) | ||
| if err != nil { | ||
| return []artifact{}, fmt.Errorf("Failed perform GraphQL query: %v", err) | ||
| } | ||
| var parsedResp struct { | ||
| Data struct { | ||
| Build struct { | ||
| Jobs struct { | ||
| Count int `json:"count"` | ||
| Edges []struct { | ||
| Node struct { | ||
| Artifacts struct { | ||
| Count int `json:"count"` | ||
| Edges []struct { | ||
| Node struct { | ||
| ID string `json:"id"` | ||
| Path string `json:"path"` | ||
| Size int `json:"size"` | ||
| DownloadURL string `json:"downloadURL"` | ||
| } `json:"node"` | ||
| } `json:"edges"` | ||
| } `json:"artifacts"` | ||
| } `json:"node"` | ||
| } `json:"edges"` | ||
| } `json:"jobs"` | ||
| } `json:"build"` | ||
| } `json:"data"` | ||
| } | ||
| if err = resp.DecodeInto(&parsedResp); err != nil { | ||
| return []artifact{}, fmt.Errorf("Failed to parse GraphQL response: %v", err) | ||
| } | ||
| if parsedResp.Data.Build.Jobs.Count > artifactDownloadLimit { | ||
| return []artifact{}, fmt.Errorf("Too many jobs\n\nBuild has %d jobs but this tool can only download %d", | ||
| parsedResp.Data.Build.Jobs.Count, artifactDownloadLimit) | ||
| } | ||
| var artifacts []artifact | ||
| for _, jobEdge := range parsedResp.Data.Build.Jobs.Edges { | ||
| if jobEdge.Node.Artifacts.Count > artifactDownloadLimit { | ||
| return []artifact{}, fmt.Errorf("Too many artifacts\n\nJob has %d artifacts but this tool can only download %d", | ||
| jobEdge.Node.Artifacts.Count, artifactDownloadLimit) | ||
| } | ||
| for _, artifactEdge := range jobEdge.Node.Artifacts.Edges { | ||
| artifacts = append(artifacts, artifact{ | ||
| ID: artifactEdge.Node.ID, | ||
| Path: artifactEdge.Node.Path, | ||
| Size: artifactEdge.Node.Size, | ||
| DownloadURL: artifactEdge.Node.DownloadURL, | ||
| }) | ||
| } | ||
| } | ||
| return artifacts, nil | ||
| } | ||
| func downloadArtifact(artifact artifact) error { | ||
| path := artifact.Path | ||
| dir := filepath.Dir(artifact.Path) | ||
| err := os.MkdirAll(dir, 0755) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := http.Get(artifact.DownloadURL) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer resp.Body.Close() | ||
| out, err := os.Create(path) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer out.Close() | ||
| _, err = io.Copy(out, resp.Body) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| } |
+9
-0
@@ -7,2 +7,11 @@ # Changelog | ||
| ## [v1.1.0](https://github.com/buildkite/cli/tree/v1.1.0) (2020-05-08) | ||
| [Full Changelog](https://github.com/buildkite/cli/compare/v1.0.0...v1.1.0) | ||
| ### Changed | ||
| - Fix local pipeline running for Windows [#73](https://github.com/buildkite/cli/pull/73) (@crufter) | ||
| - Add --listen-port to allow a stable port to be chosen [#70](https://github.com/buildkite/cli/pull/70) [#71](https://github.com/buildkite/cli/pull/71) (@petemounce) | ||
| - Update github.com/99designs/keyring to v1.1.3 [#69](https://github.com/buildkite/cli/pull/69) (@lox) | ||
| ## [v1.0.0](https://github.com/buildkite/cli/tree/v1.0.0) (2019-06-21) | ||
@@ -9,0 +18,0 @@ [Full Changelog](https://github.com/buildkite/cli/compare/v0.5.0...v1.0.0) |
+2
-0
@@ -31,2 +31,3 @@ package cli | ||
| DryRun bool | ||
| ListenPort int | ||
| } | ||
@@ -81,2 +82,3 @@ | ||
| StepFilter: ctx.StepFilterRegex, | ||
| ListenPort: ctx.ListenPort, | ||
| JobTemplate: local.Job{ | ||
@@ -83,0 +85,0 @@ Commit: commit, |
+32
-0
@@ -290,2 +290,30 @@ package main | ||
| // -------------------------- | ||
| // artifact commands | ||
| artifactCmd := app.Command("artifact", "Operate on artifacts") | ||
| artifactDownloadCtx := cli.ArtifactDownloadCommandContext{} | ||
| artifactDownloadCmd := artifactCmd. | ||
| Command("download", "Download buildkite artifacts"). | ||
| Default(). | ||
| Action(func(c *kingpin.ParseContext) error { | ||
| artifactDownloadCtx.Debug = debug | ||
| artifactDownloadCtx.Keyring = keyringImpl | ||
| artifactDownloadCtx.TerminalContext = &cli.Terminal{} | ||
| return cli.ArtifactDownloadCommand(artifactDownloadCtx) | ||
| }) | ||
| artifactDownloadCmd. | ||
| Flag("build", "Build to search for artifacts"). | ||
| StringVar(&artifactDownloadCtx.Build) | ||
| artifactDownloadCmd. | ||
| Flag("job", "Job to search for artifacts"). | ||
| StringVar(&artifactDownloadCtx.Job) | ||
| artifactDownloadCmd. | ||
| Arg("pattern", "Download only artifacts matching the glob patterm"). | ||
| StringVar(&artifactDownloadCtx.Pattern) | ||
| // -------------------------- | ||
| // local command | ||
@@ -324,2 +352,6 @@ | ||
| cmd. | ||
| Flag("listen-port", "A specific port for the local API server to listen on"). | ||
| IntVar(&runCmdCtx.ListenPort) | ||
| cmd. | ||
| Arg("file", "A specific pipeline file to upload"). | ||
@@ -326,0 +358,0 @@ FileVar(&runCmdCtx.File) |
+8
-13
| module github.com/buildkite/cli | ||
| require ( | ||
| github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f | ||
| github.com/99designs/keyring v1.1.3 | ||
| github.com/ahmetb/go-cursor v0.0.0-20131010032410-8136607ea412 | ||
@@ -14,5 +14,3 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect | ||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect | ||
| github.com/danieljoos/wincred v1.0.1 // indirect | ||
| github.com/davecgh/go-spew v1.1.1 | ||
| github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae // indirect | ||
| github.com/fatih/color v1.7.0 | ||
@@ -24,6 +22,4 @@ github.com/go-test/deep v1.0.1 | ||
| github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect | ||
| github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect | ||
| github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect | ||
| github.com/keybase/go-keychain v0.0.0-20180801170200-15d3657f24fc // indirect | ||
| github.com/kr/pretty v0.1.0 // indirect | ||
| github.com/keybase/go-keychain v0.0.0-20191022214133-1c06e666bc46 // indirect | ||
| github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect | ||
@@ -34,3 +30,4 @@ github.com/lunixbochs/vtclean v0.0.0-20170504063817-d14193dfc626 // indirect | ||
| github.com/mattn/go-isatty v0.0.3 // indirect | ||
| github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 | ||
| github.com/mitchellh/go-homedir v1.1.0 | ||
| github.com/mattn/go-zglob v0.0.1 | ||
| github.com/pmezard/go-difflib v1.0.0 // indirect | ||
@@ -40,12 +37,10 @@ github.com/sahilm/fuzzy v0.0.5 | ||
| github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c | ||
| github.com/stretchr/objx v0.1.1 // indirect | ||
| github.com/stretchr/testify v1.2.2 // indirect | ||
| golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 | ||
| golang.org/x/net v0.0.0-20180530034148-89e543239a64 // indirect | ||
| golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf | ||
| golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b | ||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect | ||
| golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43 // indirect | ||
| golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect | ||
| google.golang.org/appengine v1.0.0 // indirect | ||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6 | ||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | ||
| ) | ||
| go 1.13 |
+33
-0
| github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f h1:WXiWWJrYCaOaYimBAXlRdRJ7qOisrYyMLYnCvvhHVms= | ||
| github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f/go.mod h1:aKt8W/yd91/xHY6ixZAJZ2vYbhr3pP8DcrvuGSGNPJk= | ||
| github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY= | ||
| github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= | ||
| github.com/ahmetb/go-cursor v0.0.0-20131010032410-8136607ea412 h1:mjEdk5IWaOUyDfmIScVahVtW56YQ1gBv8RMyHl69Z30= | ||
@@ -23,2 +25,5 @@ github.com/ahmetb/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:6/fH+MoHXlGOc3iy8TSNB4eM1oaBDMs1oxPVN40M3h0= | ||
| github.com/danieljoos/wincred v1.0.1/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= | ||
| github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= | ||
| github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= | ||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
@@ -28,2 +33,4 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= | ||
| github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU= | ||
| github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= | ||
| github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||
@@ -33,2 +40,3 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||
| github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||
| github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= | ||
| github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= | ||
@@ -48,2 +56,6 @@ github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= | ||
| github.com/keybase/go-keychain v0.0.0-20180801170200-15d3657f24fc/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= | ||
| github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= | ||
| github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= | ||
| github.com/keybase/go-keychain v0.0.0-20191022214133-1c06e666bc46 h1:Sf/pnA7dyCXbGM4scY7MvmkRyHml+N35u7f9kx6+Wf0= | ||
| github.com/keybase/go-keychain v0.0.0-20191022214133-1c06e666bc46/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= | ||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||
@@ -65,4 +77,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||
| github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | ||
| github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY= | ||
| github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= | ||
| github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 h1:Y94YB7jrsihrbGSqRNMwRWJ2/dCxr0hdC2oPRohkx0A= | ||
| github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
| github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= | ||
| github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
@@ -76,10 +92,20 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
| github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= | ||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
| github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= | ||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
| github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | ||
| github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
| golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= | ||
| golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
| golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= | ||
| golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
| golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= | ||
| golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
| golang.org/x/net v0.0.0-20180530034148-89e543239a64 h1:otRUcJnCzmletog6NvNtimZZStU31VhmAuuno53i0Nk= | ||
| golang.org/x/net v0.0.0-20180530034148-89e543239a64/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= | ||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
| golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b h1:nCwwlzLoBQhkY/S3CJ2CGAU4pYfR8+5/TPGEHT+p5Nk= | ||
@@ -91,2 +117,9 @@ golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||
| golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
| golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= | ||
| golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
| golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE= | ||
| golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
| google.golang.org/appengine v1.0.0 h1:dN4LljjBKVChsv0XCSI+zbyzdqrkEwX5LQFUMRSGqOc= | ||
@@ -93,0 +126,0 @@ google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
+41
-7
@@ -13,2 +13,3 @@ package local | ||
| "regexp" | ||
| "runtime" | ||
| "strings" | ||
@@ -32,2 +33,3 @@ "sync" | ||
| JobTemplate Job | ||
| ListenPort int | ||
| } | ||
@@ -37,3 +39,3 @@ | ||
| agentPool := newAgentPool() | ||
| server := newApiServer(agentPool) | ||
| server := newApiServer(agentPool, params.ListenPort) | ||
| steps := newStepQueue() | ||
@@ -417,2 +419,16 @@ | ||
| // Windows requires certain env variables to be present | ||
| if runtime.GOOS == "windows" { | ||
| cmd.Env = append(cmd.Env, | ||
| "PATH="+os.Getenv("PATH"), | ||
| "SystemRoot="+os.Getenv("SystemRoot"), | ||
| "WINDIR="+os.Getenv("WINDIR"), | ||
| "COMSPEC="+os.Getenv("COMSPEC"), | ||
| "PATHEXT="+os.Getenv("PATHEXT"), | ||
| "TMP="+os.Getenv("TMP"), | ||
| "TEMP="+os.Getenv("TEMP"), | ||
| "SYSTEMDRIVE="+os.Getenv("SYSTEMDRIVE"), | ||
| ) | ||
| } | ||
| // this function is called at the end of Run() | ||
@@ -422,3 +438,9 @@ // it kills the agent | ||
| defer os.Remove(bootstrap.Name()) | ||
| _ = cmd.Process.Signal(os.Interrupt) | ||
| switch runtime.GOOS { | ||
| case "windows": | ||
| _ = cmd.Process.Kill() | ||
| default: | ||
| _ = cmd.Process.Signal(os.Interrupt) | ||
| } | ||
| return cmd.Wait() | ||
@@ -455,3 +477,7 @@ } | ||
| func createAgentBootstrap(checkoutPath string) (*os.File, error) { | ||
| tmpFile, err := ioutil.TempFile(os.TempDir(), "bootstrap-") | ||
| tempFileNamePattern := "bootstrap-" | ||
| if runtime.GOOS == "windows" { | ||
| tempFileNamePattern = "bootstrap-*.bat" | ||
| } | ||
| tmpFile, err := ioutil.TempFile(os.TempDir(), tempFileNamePattern) | ||
| if err != nil { | ||
@@ -463,6 +489,14 @@ return nil, err | ||
| text := []byte(fmt.Sprintf(`#!/bin/sh | ||
| export BUILDKITE_BUILD_CHECKOUT_PATH=%s | ||
| export BUILDKITE_BOOTSTRAP_PHASES=plugin,command | ||
| buildkite-agent bootstrap`, checkoutPath)) | ||
| var text []byte | ||
| if runtime.GOOS == "windows" { | ||
| text = []byte(fmt.Sprintf(`@ECHO OFF | ||
| SET "BUILDKITE_BUILD_CHECKOUT_PATH=%s" | ||
| SET "BUILDKITE_BOOTSTRAP_PHASES=plugin,command" | ||
| buildkite-agent bootstrap`, checkoutPath)) | ||
| } else { | ||
| text = []byte(fmt.Sprintf(`#!/bin/sh | ||
| export BUILDKITE_BUILD_CHECKOUT_PATH=%s | ||
| export BUILDKITE_BOOTSTRAP_PHASES=plugin,command | ||
| buildkite-agent bootstrap`, checkoutPath)) | ||
| } | ||
@@ -469,0 +503,0 @@ if _, err = tmpFile.Write(text); err != nil { |
+4
-2
@@ -85,2 +85,3 @@ package local | ||
| listener net.Listener | ||
| listenPort int | ||
@@ -93,3 +94,3 @@ sync.Mutex | ||
| func newApiServer(agentPool *agentPool) *apiServer { | ||
| func newApiServer(agentPool *agentPool, listenPort int) *apiServer { | ||
| return &apiServer{ | ||
@@ -101,2 +102,3 @@ agents: agentPool, | ||
| metadata: newOrderedMap(), | ||
| listenPort: listenPort, | ||
| } | ||
@@ -863,3 +865,3 @@ } | ||
| var err error | ||
| a.listener, err = net.Listen("tcp", "127.0.0.1:0") | ||
| a.listener, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", a.listenPort)) | ||
| if err != nil { | ||
@@ -866,0 +868,0 @@ return "", err |
+4
-4
@@ -7,5 +7,5 @@ # bk - The Buildkite CLI | ||
| ## 💬 Pre-Release Feedback | ||
| ## 💬 Feedback | ||
| This is currently a pre-release, and we'd love to hear any feedback and questions you might have. Please [file an issue on GitHub](https://github.com/buildkite/cli/issues) and let us know 💖 | ||
| We'd love to hear any feedback and questions you might have. Please [file an issue on GitHub](https://github.com/buildkite/cli/issues) and let us know 💖 | ||
@@ -16,3 +16,3 @@ ## ⬇️ Installation | ||
| ``` | ||
| ```bash | ||
| brew tap buildkite/cli | ||
@@ -50,3 +50,3 @@ brew install bk | ||
| ``` | ||
| ```bash | ||
| export GO111MODULE=on | ||
@@ -53,0 +53,0 @@ git clone git@github.com:buildkite/cli.git |
+13
-1
@@ -78,2 +78,3 @@ package cli | ||
| Start(msg string) | ||
| Println(msg string) | ||
| Success(msg string) | ||
@@ -89,2 +90,3 @@ Failure(msg string) | ||
| terminal *Terminal | ||
| message string | ||
| spinner Spinner | ||
@@ -94,6 +96,16 @@ } | ||
| func (t *tryPrompt) Start(msg string) { | ||
| t.terminal.Printf(color.WhiteString("%s: "), msg) | ||
| t.message = msg | ||
| t.terminal.Printf(color.WhiteString("%s: ", t.message)) | ||
| t.spinner.Start() | ||
| } | ||
| func (t *tryPrompt) Println(msg string) { | ||
| t.spinner.Stop() | ||
| fmt.Printf("\r") | ||
| fmt.Printf(cursor.ClearEntireLine()) | ||
| t.terminal.Println(msg) | ||
| t.terminal.Printf(color.WhiteString("%s: ", t.message)) | ||
| t.spinner.Start() | ||
| } | ||
| func (t *tryPrompt) Success(msg string) { | ||
@@ -100,0 +112,0 @@ t.spinner.Stop() |
+1
-1
@@ -5,3 +5,3 @@ package cli | ||
| // Version is the version of the CLI tool | ||
| Version = "1.0.0" | ||
| Version = "1.1.0" | ||
| ) | ||
@@ -8,0 +8,0 @@ |