github.com/chenbh/concourse/v6@v6.4.2/fly/commands/builds.go (about)

     1  package commands
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/chenbh/concourse/v6/atc"
    11  	"github.com/chenbh/concourse/v6/fly/commands/internal/displayhelpers"
    12  	"github.com/chenbh/concourse/v6/fly/commands/internal/flaghelpers"
    13  	"github.com/chenbh/concourse/v6/fly/rc"
    14  	"github.com/chenbh/concourse/v6/fly/ui"
    15  	"github.com/chenbh/concourse/v6/go-concourse/concourse"
    16  	"github.com/fatih/color"
    17  )
    18  
    19  const timeDateLayout = "2006-01-02@15:04:05-0700"
    20  const inputTimeLayout = "2006-01-02 15:04:05"
    21  
    22  type BuildsCommand struct {
    23  	AllTeams    bool                     `short:"a" long:"all-teams" description:"Show builds for the all teams that user has access to"`
    24  	Count       int                      `short:"c" long:"count" default:"50" description:"Number of builds you want to limit the return to"`
    25  	CurrentTeam bool                     `long:"current-team" description:"Show builds for the currently targeted team"`
    26  	Job         flaghelpers.JobFlag      `short:"j" long:"job" value-name:"PIPELINE/JOB" description:"Name of a job to get builds for"`
    27  	Json        bool                     `long:"json" description:"Print command result as JSON"`
    28  	Pipeline    flaghelpers.PipelineFlag `short:"p" long:"pipeline" description:"Name of a pipeline to get builds for"`
    29  	Teams       []string                 `short:"n"  long:"team" description:"Show builds for these teams"`
    30  	Since       string                   `long:"since" description:"Start of the range to filter builds"`
    31  	Until       string                   `long:"until" description:"End of the range to filter builds"`
    32  }
    33  
    34  func (command *BuildsCommand) Execute([]string) error {
    35  	var (
    36  		builds = make([]atc.Build, 0)
    37  		teams  = make([]concourse.Team, 0)
    38  
    39  		timeSince time.Time
    40  		timeUntil time.Time
    41  		page      = concourse.Page{}
    42  	)
    43  
    44  	target, err := rc.LoadTarget(Fly.Target, Fly.Verbose)
    45  	if err != nil {
    46  		return err
    47  	}
    48  
    49  	err = target.Validate()
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	page, err = command.validateBuildArguments(timeSince, page, timeUntil)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	page.Limit = command.Count
    60  	page.Timestamps = command.Since != "" || command.Until != ""
    61  
    62  	currentTeam := target.Team()
    63  	client := target.Client()
    64  
    65  	builds, err = command.getBuilds(builds, currentTeam, page, client, teams)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	return command.displayBuilds(builds)
    71  }
    72  
    73  func (command *BuildsCommand) getBuilds(builds []atc.Build, currentTeam concourse.Team, page concourse.Page, client concourse.Client, teams []concourse.Team) ([]atc.Build, error) {
    74  	var err error
    75  	if command.pipelineFlag() {
    76  		builds, err = command.validatePipelineBuilds(builds, currentTeam, page)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  	} else if command.jobFlag() {
    81  		builds, err = command.validateJobBuilds(builds, currentTeam, page)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  	} else if command.AllTeams {
    86  		teams, err = command.getAllTeams(client, teams)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  	} else if len(command.Teams) > 0 || command.CurrentTeam {
    91  		teams = command.validateCurrentTeam(teams, currentTeam, client)
    92  	} else {
    93  		builds, _, err = client.Builds(page)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  	}
    98  
    99  	for _, team := range teams {
   100  		teamBuilds, _, err := team.Builds(page)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  
   105  		builds = append(builds, teamBuilds...)
   106  	}
   107  
   108  	return builds, err
   109  }
   110  
   111  func (command *BuildsCommand) getAllTeams(client concourse.Client, teams []concourse.Team) ([]concourse.Team, error) {
   112  	atcTeams, err := client.ListTeams()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	for _, atcTeam := range atcTeams {
   117  		teams = append(teams, client.Team(atcTeam.Name))
   118  	}
   119  	return teams, nil
   120  }
   121  
   122  func (command *BuildsCommand) validateCurrentTeam(teams []concourse.Team, currentTeam concourse.Team, client concourse.Client) []concourse.Team {
   123  	if command.CurrentTeam {
   124  		teams = append(teams, currentTeam)
   125  	}
   126  	for _, teamName := range command.Teams {
   127  		teams = append(teams, client.Team(teamName))
   128  	}
   129  	return teams
   130  }
   131  
   132  func (command *BuildsCommand) validateJobBuilds(builds []atc.Build, currentTeam concourse.Team, page concourse.Page) ([]atc.Build, error) {
   133  	var (
   134  		err   error
   135  		found bool
   136  	)
   137  
   138  	builds, _, found, err = currentTeam.JobBuilds(
   139  		command.Job.PipelineName,
   140  		command.Job.JobName,
   141  		page,
   142  	)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	if !found {
   147  		displayhelpers.Failf("pipeline/job not found")
   148  	}
   149  	return builds, err
   150  }
   151  
   152  func (command *BuildsCommand) validatePipelineBuilds(builds []atc.Build, currentTeam concourse.Team, page concourse.Page) ([]atc.Build, error) {
   153  	_, err := command.Pipeline.Validate()
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	var found bool
   159  	builds, _, found, err = currentTeam.PipelineBuilds(
   160  		string(command.Pipeline),
   161  		page,
   162  	)
   163  
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	if !found {
   169  		displayhelpers.Failf("pipeline not found")
   170  	}
   171  
   172  	return builds, err
   173  }
   174  
   175  func (command *BuildsCommand) displayBuilds(builds []atc.Build) error {
   176  	var err error
   177  	if command.Json {
   178  		err = displayhelpers.JsonPrint(builds)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		return nil
   183  	}
   184  
   185  	table := ui.Table{
   186  		Headers: ui.TableRow{
   187  			{Contents: "id", Color: color.New(color.Bold)},
   188  			{Contents: "pipeline/job", Color: color.New(color.Bold)},
   189  			{Contents: "build", Color: color.New(color.Bold)},
   190  			{Contents: "status", Color: color.New(color.Bold)},
   191  			{Contents: "start", Color: color.New(color.Bold)},
   192  			{Contents: "end", Color: color.New(color.Bold)},
   193  			{Contents: "duration", Color: color.New(color.Bold)},
   194  			{Contents: "team", Color: color.New(color.Bold)},
   195  		},
   196  	}
   197  
   198  	buildCap := command.buildCap(builds)
   199  	for _, b := range builds[:buildCap] {
   200  		startTimeCell, endTimeCell, durationCell := populateTimeCells(time.Unix(b.StartTime, 0), time.Unix(b.EndTime, 0))
   201  
   202  		var pipelineJobCell, buildCell ui.TableCell
   203  		if b.PipelineName == "" {
   204  			pipelineJobCell.Contents = "one-off"
   205  			buildCell.Contents = "n/a"
   206  		} else {
   207  			pipelineJobCell.Contents = fmt.Sprintf("%s/%s", b.PipelineName, b.JobName)
   208  			buildCell.Contents = b.Name
   209  		}
   210  
   211  		var statusCell ui.TableCell
   212  		statusCell.Contents = b.Status
   213  
   214  		switch b.Status {
   215  		case "pending":
   216  			statusCell.Color = ui.PendingColor
   217  		case "started":
   218  			statusCell.Color = ui.StartedColor
   219  		case "succeeded":
   220  			statusCell.Color = ui.SucceededColor
   221  		case "failed":
   222  			statusCell.Color = ui.FailedColor
   223  		case "errored":
   224  			statusCell.Color = ui.ErroredColor
   225  		case "aborted":
   226  			statusCell.Color = ui.AbortedColor
   227  		case "paused":
   228  			statusCell.Color = ui.PausedColor
   229  		}
   230  
   231  		table.Data = append(table.Data, []ui.TableCell{
   232  			{Contents: strconv.Itoa(b.ID)},
   233  			pipelineJobCell,
   234  			buildCell,
   235  			statusCell,
   236  			startTimeCell,
   237  			endTimeCell,
   238  			durationCell,
   239  			{Contents: b.TeamName},
   240  		})
   241  	}
   242  
   243  	return table.Render(os.Stdout, Fly.PrintTableHeaders)
   244  }
   245  
   246  func (command *BuildsCommand) validateBuildArguments(timeSince time.Time, page concourse.Page, timeUntil time.Time) (concourse.Page, error) {
   247  	var err error
   248  	if command.Since != "" {
   249  		timeSince, err = time.ParseInLocation(inputTimeLayout, command.Since, time.Now().Location())
   250  		if err != nil {
   251  			return page, errors.New("Since time should be in the format: " + inputTimeLayout)
   252  		}
   253  		page.Since = int(timeSince.Unix())
   254  	}
   255  	if command.Until != "" {
   256  		timeUntil, err = time.ParseInLocation(inputTimeLayout, command.Until, time.Now().Location())
   257  		if err != nil {
   258  			return page, errors.New("Until time should be in the format: " + inputTimeLayout)
   259  		}
   260  		page.Until = int(timeUntil.Unix())
   261  	}
   262  	if timeSince.After(timeUntil) && command.Since != "" && command.Until != "" {
   263  		return page, errors.New("Cannot have --since after --until")
   264  	}
   265  	if command.pipelineFlag() && command.jobFlag() {
   266  		return page, errors.New("Cannot specify both --pipeline and --job")
   267  	}
   268  	if command.CurrentTeam && command.AllTeams {
   269  		return page, errors.New("Cannot specify both --all-teams and --current-team")
   270  	}
   271  	if len(command.Teams) > 0 && command.AllTeams {
   272  		return page, errors.New("Cannot specify both --all-teams and --team")
   273  	}
   274  	return page, err
   275  }
   276  
   277  func populateTimeCells(startTime time.Time, endTime time.Time) (ui.TableCell, ui.TableCell, ui.TableCell) {
   278  	var startTimeCell ui.TableCell
   279  	var endTimeCell ui.TableCell
   280  	var durationCell ui.TableCell
   281  
   282  	startTime = startTime.Local()
   283  	endTime = endTime.Local()
   284  	zeroTime := time.Unix(0, 0)
   285  
   286  	if startTime == zeroTime {
   287  		startTimeCell.Contents = "n/a"
   288  	} else {
   289  		startTimeCell.Contents = startTime.Format(timeDateLayout)
   290  	}
   291  
   292  	if endTime == zeroTime {
   293  		endTimeCell.Contents = "n/a"
   294  		durationCell.Contents = fmt.Sprintf("%v+", roundSecondsOffDuration(time.Since(startTime)))
   295  	} else {
   296  		endTimeCell.Contents = endTime.Format(timeDateLayout)
   297  		durationCell.Contents = endTime.Sub(startTime).String()
   298  	}
   299  
   300  	if startTime == zeroTime {
   301  		durationCell.Contents = "n/a"
   302  	}
   303  
   304  	return startTimeCell, endTimeCell, durationCell
   305  }
   306  
   307  func roundSecondsOffDuration(d time.Duration) time.Duration {
   308  	return d - (d % time.Second)
   309  }
   310  
   311  func (command *BuildsCommand) jobFlag() bool {
   312  	return command.Job.PipelineName != "" && command.Job.JobName != ""
   313  }
   314  
   315  func (command *BuildsCommand) pipelineFlag() bool {
   316  	return command.Pipeline != ""
   317  }
   318  
   319  func (command *BuildsCommand) buildCap(builds []atc.Build) int {
   320  	if command.Count < len(builds) {
   321  		return command.Count
   322  	}
   323  
   324  	return len(builds)
   325  }