github.com/matthewdale/lab@v0.14.0/cmd/ci_trace.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "log" 10 "os" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 "github.com/zaquestion/lab/internal/git" 18 lab "github.com/zaquestion/lab/internal/gitlab" 19 ) 20 21 // ciLintCmd represents the lint command 22 var ciTraceCmd = &cobra.Command{ 23 Use: "trace [remote [[branch:]job]]", 24 Aliases: []string{"logs"}, 25 Short: "Trace the output of a ci job", 26 Long: `If a job is not specified the latest running job or last job in the pipeline is used`, 27 Run: func(cmd *cobra.Command, args []string) { 28 var ( 29 remote string 30 jobName string 31 ) 32 33 branch, err := git.CurrentBranch() 34 if err != nil { 35 log.Fatal(err) 36 } 37 if len(args) > 1 { 38 jobName = args[1] 39 if strings.Contains(args[1], ":") { 40 ps := strings.Split(args[1], ":") 41 branch, jobName = ps[0], ps[1] 42 } 43 } 44 remote = determineSourceRemote(branch) 45 if len(args) > 0 { 46 ok, err := git.IsRemote(args[0]) 47 if err != nil || !ok { 48 log.Fatal(args[0], " is not a remote:", err) 49 } 50 remote = args[0] 51 } 52 53 rn, err := git.PathWithNameSpace(remote) 54 if err != nil { 55 log.Fatal(err) 56 } 57 project, err := lab.FindProject(rn) 58 if err != nil { 59 log.Fatal(err) 60 } 61 err = doTrace(context.Background(), os.Stdout, project.ID, branch, jobName) 62 if err != nil { 63 log.Fatal(err) 64 } 65 }, 66 } 67 68 func doTrace(ctx context.Context, w io.Writer, pid interface{}, branch, name string) error { 69 var ( 70 once sync.Once 71 offset int64 72 ) 73 for range time.NewTicker(time.Second * 3).C { 74 if ctx.Err() == context.Canceled { 75 break 76 } 77 trace, job, err := lab.CITrace(pid, branch, name) 78 if job == nil { 79 return errors.Wrap(err, "failed to find job") 80 } 81 switch job.Status { 82 case "pending": 83 fmt.Fprintf(w, "%s is pending... waiting for job to start\n", job.Name) 84 continue 85 case "manual": 86 fmt.Fprintf(w, "Manual job %s not started, waiting for job to start\n", job.Name) 87 continue 88 } 89 once.Do(func() { 90 if name == "" { 91 name = job.Name 92 } 93 fmt.Fprintf(w, "Showing logs for %s job #%d\n", job.Name, job.ID) 94 }) 95 // TODO: can trace be passed directly to the readseaker? 96 buf, err := ioutil.ReadAll(trace) 97 if err != nil { 98 return err 99 } 100 r := bytes.NewReader(buf) 101 r.Seek(offset, io.SeekStart) 102 new, err := ioutil.ReadAll(r) 103 if err != nil { 104 return err 105 } 106 107 offset += int64(len(new)) 108 fmt.Fprint(w, string(new)) 109 if job.Status == "success" || 110 job.Status == "failed" || 111 job.Status == "cancelled" { 112 return nil 113 } 114 } 115 return nil 116 } 117 118 func init() { 119 ciCmd.AddCommand(ciTraceCmd) 120 }