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 }