github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/commands/builds.go (about)

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