From bceda03a2a0ab7e59b0c1f08756931afe47559f3 Mon Sep 17 00:00:00 2001 From: Zaq? Wiedmann Date: Fri, 28 Dec 2018 01:58:05 -0800 Subject: [PATCH] (ci status) support a short text based pipeline/job status To address #261 & through the use of `--wait` hopefully #240 --- cmd/ci_status.go | 90 +++++++++++++++++++++++++++++++++++++++ cmd/ci_status_test.go | 65 ++++++++++++++++++++++++++++ go.mod | 8 ++-- go.sum | 16 +++++++ internal/gitlab/gitlab.go | 4 +- 5 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 cmd/ci_status.go create mode 100644 cmd/ci_status_test.go diff --git a/cmd/ci_status.go b/cmd/ci_status.go new file mode 100644 index 00000000..a76fe25f --- /dev/null +++ b/cmd/ci_status.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "text/tabwriter" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/zaquestion/lab/internal/git" + lab "github.com/zaquestion/lab/internal/gitlab" +) + +// ciStatusCmd represents the run command +var ciStatusCmd = &cobra.Command{ + Use: "status [branch]", + Aliases: []string{"run"}, + Short: "Textual representation of a CI pipeline", + Long: ``, + Example: `lab ci status +lab ci status --wait`, + RunE: nil, + Run: func(cmd *cobra.Command, args []string) { + branch, err := git.CurrentBranch() + if err != nil { + log.Fatal(err) + } + + if len(args) > 1 { + branch = args[1] + } + remote := determineSourceRemote(branch) + if len(args) > 0 { + ok, err := git.IsRemote(args[0]) + if err != nil || !ok { + log.Fatal(args[0], " is not a remote:", err) + } + remote = args[0] + } + rn, err := git.PathWithNameSpace(remote) + if err != nil { + log.Fatal(err) + } + pid := rn + + w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, byte(' '), 0) + jobs, err := lab.CIJobs(pid, branch) + if err != nil { + log.Fatal(errors.Wrap(err, "failed to find ci jobs")) + } + jobs = latestJobs(jobs) + + if len(jobs) == 0 { + return + } + + wait, err := cmd.Flags().GetBool("wait") + if err != nil { + log.Fatal(err) + } + + fmt.Fprintln(w, "Stage:\tName\t-\tStatus") + for { + for _, job := range jobs { + fmt.Fprintf(w, "%s:\t%s\t-\t%s\n", job.Stage, job.Name, job.Status) + } + if !wait { + break + } + if jobs[0].Pipeline.Status != "pending" && + jobs[0].Pipeline.Status != "running" { + break + } + fmt.Fprintln(w) + } + + fmt.Fprintf(w, "\nPipeline Status: %s\n", jobs[0].Pipeline.Status) + if wait && jobs[0].Pipeline.Status != "success" { + os.Exit(1) + } + w.Flush() + }, +} + +func init() { + ciStatusCmd.MarkZshCompPositionalArgumentCustom(1, "__lab_completion_remote_branches") + ciStatusCmd.Flags().Bool("wait", false, "Continuously print the status and wait to exit until the pipeline finishes. Exit code indicates pipeline status") + ciCmd.AddCommand(ciStatusCmd) +} diff --git a/cmd/ci_status_test.go b/cmd/ci_status_test.go new file mode 100644 index 00000000..886b29d6 --- /dev/null +++ b/cmd/ci_status_test.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ciStatus(t *testing.T) { + t.Parallel() + repo := copyTestRepo(t) + cmd := exec.Command("../lab_bin", "fetch", "origin") + cmd.Dir = repo + if b, err := cmd.CombinedOutput(); err != nil { + t.Log(string(b)) + t.Fatal(err) + } + + cmd = exec.Command("../lab_bin", "checkout", "origin/ci_test_pipeline") + cmd.Dir = repo + if b, err := cmd.CombinedOutput(); err != nil { + t.Log(string(b)) + t.Fatal(err) + } + + cmd = exec.Command("../lab_bin", "checkout", "-b", "ci_test_pipeline") + cmd.Dir = repo + if b, err := cmd.CombinedOutput(); err != nil { + t.Log(string(b)) + t.Fatal(err) + } + + cmd = exec.Command("../lab_bin", "ci", "status") + cmd.Dir = repo + + b, err := cmd.CombinedOutput() + if err != nil { + t.Log(string(b)) + t.Fatal(err) + } + out := string(b) + assert.Contains(t, out, `Stage: Name - Status +build: build1 - success +build: build2 - success +build: build2:fails - failed +test: test1 - success +test: test2 - success +test: test2:really_a_long_name_for - success +test: test2:no_suffix:test - success +test: test3 - success +deploy: deploy1 - success +deploy: deploy2 - manual +deploy: deploy3:no_sufix:deploy - success +deploy: deploy4 - success +deploy: deploy5:really_a_long_name_for - success +deploy: deploy5 - success +deploy: deploy6 - success +deploy: deploy7 - success +deploy: deploy8 - success +deploy: deploy9 - success +deploy: deploy10 - success`) + + assert.Contains(t, out, "Pipeline Status: success") +} diff --git a/go.mod b/go.mod index 14fe2054..1fe11ca3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/derekparker/delve v1.1.0 github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect github.com/gdamore/tcell v0.0.0-20180416163743-2f258105ca8c - github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -33,12 +32,11 @@ require ( github.com/spf13/viper v0.0.0-20180507071007-15738813a09d github.com/stretchr/testify v1.2.2 github.com/tcnksm/go-gitconfig v0.1.2 - github.com/xanzy/go-gitlab v0.11.3 + github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad // indirect + github.com/xanzy/go-gitlab v0.12.3-0.20181228114601-7bc4155e8bf8 golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 // indirect golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 - golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc // indirect - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect - google.golang.org/appengine v1.1.0 // indirect + golang.org/x/tools v0.0.0-20190107155254-e063def13b29 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 41cb7486..a5af7c9d 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= @@ -82,20 +84,31 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= github.com/xanzy/go-gitlab v0.0.0-20180921132519-8d21e61ce4a9 h1:gmCeo/bSUp/oLKpsZrICPRTgwsuI3m17l5c2X8y4/0g= github.com/xanzy/go-gitlab v0.0.0-20180921132519-8d21e61ce4a9/go.mod h1:CRKHkvFWNU6C3AEfqLWjnCNnAs4nj8Zk95rX2S3X6Mw= github.com/xanzy/go-gitlab v0.11.3 h1:gSYcSb+pCx3fco6/O3w784/omQVTcrgxRzyf14SBvUQ= github.com/xanzy/go-gitlab v0.11.3/go.mod h1:CRKHkvFWNU6C3AEfqLWjnCNnAs4nj8Zk95rX2S3X6Mw= +github.com/xanzy/go-gitlab v0.12.2 h1:WQY08U/RwrEx1i3x2Fnzsj/oCAzMYMKpk5cq+kHN4Vo= +github.com/xanzy/go-gitlab v0.12.2/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xanzy/go-gitlab v0.12.3-0.20181228114601-7bc4155e8bf8 h1:POfZqSXz6Rr3hKp1tfYDH8L28/1lDLaa6CJnFrrxY/0= +github.com/xanzy/go-gitlab v0.12.3-0.20181228114601-7bc4155e8bf8/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 h1:Pn8fQdvx+z1avAi7fdM2kRYWQNxGlavNDSyzrQg2SsU= golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20180420171155-e73bf333ef89 h1:YMKUzb2eHV8HdgAr0z9lbGN1Av2h4cgMMsvi+JV60MM= golang.org/x/crypto v0.0.0-20180420171155-e73bf333ef89/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc h1:3ElrZeO6IBP+M8kgu5YFwRo92Gqr+zBg3aooYQ6ziqU= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -104,8 +117,11 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUk golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190107155254-e063def13b29 h1:mtLB/BpwjjSIylF0++D6EG1ExPVEIcFKMMwK6HFmbtU= +golang.org/x/tools v0.0.0-20190107155254-e063def13b29/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/gitlab/gitlab.go b/internal/gitlab/gitlab.go index a7591e42..df7a5864 100644 --- a/internal/gitlab/gitlab.go +++ b/internal/gitlab/gitlab.go @@ -179,7 +179,7 @@ func MRGet(project string, mrNum int) (*gitlab.MergeRequest, error) { return nil, err } - mr, _, err := lab.MergeRequests.GetMergeRequest(p.ID, mrNum) + mr, _, err := lab.MergeRequests.GetMergeRequest(p.ID, mrNum, nil) if err != nil { return nil, err } @@ -224,7 +224,7 @@ func MRList(project string, opts gitlab.ListProjectMergeRequestsOptions, n int) // MRClose closes an mr on a GitLab project func MRClose(pid interface{}, id int) error { - mr, _, err := lab.MergeRequests.GetMergeRequest(pid, id) + mr, _, err := lab.MergeRequests.GetMergeRequest(pid, id, nil) if err != nil { return err }