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  }