Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
feat: trace pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
profclems committed Aug 13, 2020
1 parent 11aecc7 commit 90f4d8a
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 62 deletions.
62 changes: 62 additions & 0 deletions commands/pipeline.go
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/spf13/cobra"
"glab/internal/git"
"glab/internal/manip"
"io"
"math"
"os"
"text/tabwriter"

Expand Down Expand Up @@ -40,6 +42,65 @@ func displayMultiplePipelines(m []*gitlab.PipelineInfo) {
}
}

func retryPipelineJob(pid int) *gitlab.Pipeline {
gitlabClient, repo := git.InitGitlabClient()
pipe, _, err := gitlabClient.Pipelines.RetryPipelineBuild(repo, pid)
if err != nil {
er(err)
}
return pipe
}

func getPipelineJob(jid int) (*gitlab.Job, error) {
gitlabClient, repo := git.InitGitlabClient()
job, _, err := gitlabClient.Jobs.GetJob(repo, jid)
return job, err
}

func fmtDuration(duration float64) string {
s := math.Mod(duration, 60)
m := (duration - s) / 60
s = math.Round(s)
return fmt.Sprintf("%02vm %02vs", m, s)
}

func getPipelines(l *gitlab.ListProjectPipelinesOptions) ([]*gitlab.PipelineInfo, error) {
gitlabClient, repo := git.InitGitlabClient()
pipes, _, err := gitlabClient.Pipelines.ListProjectPipelines(repo, l)
if err != nil {
return nil, err
}
return pipes, nil
}

func getPipelineJobs(pid int) []*gitlab.Job {
gitlabClient, repo := git.InitGitlabClient()
l := &gitlab.ListJobsOptions{}
pipeJobs, _, err := gitlabClient.Jobs.ListPipelineJobs(repo, pid, l)
if err != nil {
er(err)
}
return pipeJobs
}

func getPipelineJobLog(jobID int) io.Reader {
gitlabClient, repo := git.InitGitlabClient()
pipeJobs, _, err := gitlabClient.Jobs.GetTraceFile(repo, jobID)
if err != nil {
er(err)
}
return pipeJobs
}

func getSinglePipeline(pid int) (*gitlab.Pipeline, error) {
gitlabClient, repo := git.InitGitlabClient()
pipes, _, err := gitlabClient.Pipelines.GetPipeline(repo, pid)
if err != nil {
return nil, err
}
return pipes, nil
}

// pipelineCmd is merge request command
var pipelineCmd = &cobra.Command{
Use: "pipeline <command> [flags]",
Expand All @@ -55,5 +116,6 @@ var pipelineCmd = &cobra.Command{
}

func init() {
pipelineCmd.PersistentFlags().StringP("repo", "R","", "Select another repository using the OWNER/REPO format or the project ID. Supports group namespaces")
RootCmd.AddCommand(pipelineCmd)
}
25 changes: 25 additions & 0 deletions commands/pipeline_ci.go
@@ -0,0 +1,25 @@
package commands

import (
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
)

var pipelineCICmd = &cobra.Command{
Use: "ci [command] [flags]",
Short: `Work with GitLab CI pipelines and jobs`,
Example: heredoc.Doc(`
$ glab pipeline ci trace
`),
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) > 1 {
cmdErr(cmd, args)
}
},
}


func init() {
pipelineCmd.AddCommand(pipelineCICmd)
}
139 changes: 139 additions & 0 deletions commands/pipeline_ci_trace.go
@@ -0,0 +1,139 @@
package commands

import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
"glab/internal/git"
"glab/internal/manip"
"io"
"io/ioutil"
"os"
"regexp"

//"strings"
"sync"
"time"

//"github.com/pkg/errors"
)

var (
once sync.Once
offset int64
)

var pipelineCITraceCmd = &cobra.Command{
Use: "trace <job-id> [flags]",
Short: `Work with GitLab CI pipelines and jobs`,
Example: heredoc.Doc(`
$ glab pipeline ci trace
`),
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 1 {
cmdErr(cmd, args)
}
var jobID int
if len(args) != 0 {
jobID = manip.StringToInt(args[0])
} else {
branch, _ := cmd.Flags().GetString("branch")
var err error
if branch == "" {
branch, err = git.CurrentBranch()
if err != nil {
er(err)
}
}
l := &gitlab.ListProjectPipelinesOptions{
Ref: gitlab.String(branch),
OrderBy: gitlab.String("updated_at"),
Sort: gitlab.String("desc"),
}
l.Page = 1
l.PerPage = 1
pipes, err := getPipelines(l)
if err != nil {
er(err)
}
if len(pipes) == 0 {
er("No pipelines running or available on " + branch + "branch")
}
pipeline := pipes[0]
jobs := getPipelineJobs(pipeline.ID)
var jobOptions []string
var selectedJob string
for _, job := range jobs {
jobOptions = append(jobOptions, fmt.Sprintf("%s (%d) - %s", job.Name, job.ID, job.Status))
}
prompt := &survey.Select{
Message: "Select pipeline job to trace:",
Options: jobOptions,
}
survey.AskOne(prompt, &selectedJob)
if selectedJob != "" {
re := regexp.MustCompile(`(?s)\((.*)\)`)
m := re.FindAllStringSubmatch(selectedJob,-1)
jobID = manip.StringToInt(m[0][1])
}

if err := runTrace(jobID); err != nil {
er(err)
}
}
},
}

func runTrace(jobID int) error {
w := os.Stdout
ctx := context.Background()
for range time.NewTicker(time.Second * 3).C {
if ctx.Err() == context.Canceled {
break
}
job, err := getPipelineJob(jobID)
trace := getPipelineJobLog(jobID)

if err != nil || job == nil || trace == nil {
return errors.Wrap(err, "failed to find job")
}

switch job.Status {
case "pending":
_, _ = fmt.Fprintf(w, "%s is pending... waiting for job to start...\n", job.Name)
return nil
case "manual":
_, _ = fmt.Fprintf(w, "Manual job %s not started, waiting for job to start\n", job.Name)
continue
case "skipped":
_, _ = fmt.Fprintf(w, "%s has been skipped\n", job.Name)
continue
}
once.Do(func() {
_, _ = fmt.Fprintf(w, "Showing logs for %s job #%d\n", job.Name, job.ID)
})
_, err = io.CopyN(ioutil.Discard, trace, offset)
lenT, err := io.Copy(w, trace)
if err != nil {
return err
}
offset += lenT

if job.Status == "success" ||
job.Status == "failed" ||
job.Status == "cancelled" {
return nil
}
}
return nil
}

func init() {
pipelineCITraceCmd.Flags().StringP("branch", "b", "", "Check pipeline status for a branch. (Default is the current branch)")
pipelineCICmd.AddCommand(pipelineCITraceCmd)
}
66 changes: 23 additions & 43 deletions commands/pipeline_status.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/xanzy/go-gitlab"
"glab/internal/git"
"math"
"strconv"
"time"
)

Expand Down Expand Up @@ -50,10 +51,10 @@ var pipelineStatusCmd = &cobra.Command{
if err != nil {
er(err)
}
runningPipeline := pipes[0]
if len(pipes) == 1 {
runningPipeline := pipes[0]
isRunning := true
retry := false
retry := "Exit"
writer := uilive.New()

// start listening for updates and render
Expand All @@ -71,6 +72,7 @@ var pipelineStatusCmd = &cobra.Command{
default:
status = color.Gray.Sprint(s)
}
//fmt.Println(job.Tag)
fmt.Fprintf(writer, "(%s) • %s\t\t%s\t\t%s\n", status, duration, job.Stage, job.Name)
}

Expand All @@ -85,61 +87,39 @@ var pipelineStatusCmd = &cobra.Command{
runningPipeline = pipes[0]
} else {
if runningPipeline.Status == "failed" || runningPipeline.Status == "canceled" {
prompt := &survey.Confirm{
Message: "Do you want to retry?",
prompt := &survey.Select{
Message: "Choose an action:",
Options: []string{"View Logs", "Retry", "Exit"},
Default: "Exit",
}
survey.AskOne(prompt, &retry)
}
if retry {
retryPipelineJob(runningPipeline.ID)
pipes, err = getPipelines(l)
runningPipeline = pipes[0]
if retry != "" && retry != "Exit" {
if retry == "View Logs" {
isRunning = false
} else {
retryPipelineJob(runningPipeline.ID)
pipes, err = getPipelines(l)
runningPipeline = pipes[0]
isRunning = true
}
} else {
isRunning = false
}
}
time.Sleep(time.Millisecond * 0)
if retry == "View Logs" {
args = []string{strconv.FormatInt(int64(runningPipeline.ID), 10)}
viewPipelines(cmd, args)
fmt.Println("logs")
}
}
} else {
er("No pipelines running on " + branch + " branch")
er("No pipelines running or available on " + branch + " branch")
}
},
}

func retryPipelineJob(pid int) *gitlab.Pipeline {
gitlabClient, repo := git.InitGitlabClient()
pipe, _, err := gitlabClient.Pipelines.RetryPipelineBuild(repo, pid)
if err != nil {
er(err)
}
return pipe
}

func fmtDuration(duration float64) string {
s := math.Mod(duration, 60)
m := (duration - s) / 60
s = math.Round(s)
return fmt.Sprintf("%02vm %02vs", m, s)
}

func getPipelines(l *gitlab.ListProjectPipelinesOptions) ([]*gitlab.PipelineInfo, error) {
gitlabClient, repo := git.InitGitlabClient()
pipes, _, err := gitlabClient.Pipelines.ListProjectPipelines(repo, l)
if err != nil {
return nil, err
}
return pipes, nil
}

func getSinglePipeline(pid int) (*gitlab.Pipeline, error) {
gitlabClient, repo := git.InitGitlabClient()
pipes, _, err := gitlabClient.Pipelines.GetPipeline(repo, pid)
if err != nil {
return nil, err
}
return pipes, nil
}

func init() {
pipelineStatusCmd.Flags().BoolP("live", "l", false, "Show status in realtime till pipeline ends")
pipelineStatusCmd.Flags().StringP("branch", "b", "", "Check pipeline status for a branch. (Default is current branch)")
Expand Down
19 changes: 0 additions & 19 deletions commands/pipeline_view.go
Expand Up @@ -30,25 +30,6 @@ var pipelineDetails *gitlab.Pipeline
var pipelineJobDetails []*gitlab.Job
var mainView *gocui.View

func getPipelineJobs(pid int) []*gitlab.Job {
gitlabClient, repo := git.InitGitlabClient()
l := &gitlab.ListJobsOptions{}
pipeJobs, _, err := gitlabClient.Jobs.ListPipelineJobs(repo, pid, l)
if err != nil {
er(err)
}
return pipeJobs
}

func getPipelineJobLog(jobID int) io.Reader {
gitlabClient, repo := git.InitGitlabClient()
pipeJobs, _, err := gitlabClient.Jobs.GetTraceFile(repo, jobID)
if err != nil {
er(err)
}
return pipeJobs
}

func viewPipelines(cmd *cobra.Command, args []string) {
if len(args) > 1 || len(args) == 0 {
cmdErr(cmd, args)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -19,6 +19,7 @@ require (
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
github.com/onsi/gomega v1.10.1 // indirect
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/tcnksm/go-gitconfig v0.1.2
Expand Down

0 comments on commit 90f4d8a

Please sign in to comment.