github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/build_factory.go (about) 1 package db 2 3 import ( 4 "database/sql" 5 "fmt" 6 "strconv" 7 "time" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/pf-qiu/concourse/v6/atc/db/lock" 11 ) 12 13 //go:generate counterfeiter . BuildFactory 14 15 type BuildFactory interface { 16 Build(int) (Build, bool, error) 17 VisibleBuilds([]string, Page) ([]Build, Pagination, error) 18 AllBuilds(Page) ([]Build, Pagination, error) 19 PublicBuilds(Page) ([]Build, Pagination, error) 20 GetAllStartedBuilds() ([]Build, error) 21 GetDrainableBuilds() ([]Build, error) 22 // TODO: move to BuildLifecycle, new interface (see WorkerLifecycle) 23 MarkNonInterceptibleBuilds() error 24 } 25 26 type buildFactory struct { 27 conn Conn 28 lockFactory lock.LockFactory 29 oneOffGracePeriod time.Duration 30 failedGracePeriod time.Duration 31 } 32 33 func NewBuildFactory(conn Conn, lockFactory lock.LockFactory, oneOffGracePeriod time.Duration, failedGracePeriod time.Duration) BuildFactory { 34 return &buildFactory{ 35 conn: conn, 36 lockFactory: lockFactory, 37 oneOffGracePeriod: oneOffGracePeriod, 38 failedGracePeriod: failedGracePeriod, 39 } 40 } 41 42 func (f *buildFactory) Build(buildID int) (Build, bool, error) { 43 build := newEmptyBuild(f.conn, f.lockFactory) 44 row := buildsQuery. 45 Where(sq.Eq{"b.id": buildID}). 46 RunWith(f.conn). 47 QueryRow() 48 49 err := scanBuild(build, row, f.conn.EncryptionStrategy()) 50 if err != nil { 51 if err == sql.ErrNoRows { 52 return nil, false, nil 53 } 54 return nil, false, err 55 } 56 57 return build, true, nil 58 } 59 60 func (f *buildFactory) VisibleBuilds(teamNames []string, page Page) ([]Build, Pagination, error) { 61 newBuildsQuery := buildsQuery. 62 Where(sq.Or{ 63 sq.Eq{"p.public": true}, 64 sq.Eq{"t.name": teamNames}, 65 }) 66 67 if page.UseDate { 68 return getBuildsWithDates(newBuildsQuery, minMaxIdQuery, page, f.conn, 69 f.lockFactory) 70 } 71 return getBuildsWithPagination(newBuildsQuery, minMaxIdQuery, page, f.conn, 72 f.lockFactory) 73 } 74 75 func (f *buildFactory) AllBuilds(page Page) ([]Build, Pagination, error) { 76 if page.UseDate { 77 return getBuildsWithDates(buildsQuery, minMaxIdQuery, page, f.conn, 78 f.lockFactory) 79 } 80 return getBuildsWithPagination(buildsQuery, minMaxIdQuery, 81 page, f.conn, f.lockFactory) 82 } 83 84 func (f *buildFactory) PublicBuilds(page Page) ([]Build, Pagination, error) { 85 return getBuildsWithPagination( 86 buildsQuery.Where(sq.Eq{"p.public": true}), minMaxIdQuery, 87 page, f.conn, f.lockFactory) 88 } 89 90 func (f *buildFactory) MarkNonInterceptibleBuilds() error { 91 _, err := psql.Update("builds b"). 92 Set("interceptible", false). 93 Where(sq.Eq{ 94 "completed": true, 95 "interceptible": true, 96 }). 97 Where(sq.Or{ 98 sq.NotEq{"job_id": nil}, 99 sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.oneOffGracePeriod.Seconds()))), 100 }). 101 Where(f.constructBuildFilter()). 102 RunWith(f.conn). 103 Exec() 104 return err 105 } 106 107 func (f *buildFactory) constructBuildFilter() sq.Or { 108 buildFilter := sq.Or{ 109 sq.Expr("NOT EXISTS (SELECT 1 FROM jobs j WHERE j.latest_completed_build_id = b.id)"), 110 sq.Eq{"status": string(BuildStatusSucceeded)}, 111 } 112 if f.failedGracePeriod > 0 { // if zero, grace period is disabled 113 buildFilter = append(buildFilter, 114 sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.failedGracePeriod.Seconds())))) 115 } 116 return buildFilter 117 } 118 119 func (f *buildFactory) GetDrainableBuilds() ([]Build, error) { 120 query := buildsQuery.Where(sq.Eq{ 121 "b.completed": true, 122 "b.drained": false, 123 }) 124 125 return getBuilds(query, f.conn, f.lockFactory) 126 } 127 128 func (f *buildFactory) GetAllStartedBuilds() ([]Build, error) { 129 query := buildsQuery.Where(sq.Eq{ 130 "b.status": BuildStatusStarted, 131 }) 132 133 return getBuilds(query, f.conn, f.lockFactory) 134 } 135 136 func getBuilds(buildsQuery sq.SelectBuilder, conn Conn, lockFactory lock.LockFactory) ([]Build, error) { 137 rows, err := buildsQuery.RunWith(conn).Query() 138 if err != nil { 139 return nil, err 140 } 141 142 defer Close(rows) 143 144 bs := []Build{} 145 146 for rows.Next() { 147 b := newEmptyBuild(conn, lockFactory) 148 err := scanBuild(b, rows, conn.EncryptionStrategy()) 149 if err != nil { 150 return nil, err 151 } 152 153 bs = append(bs, b) 154 } 155 156 return bs, nil 157 } 158 159 func getBuildsWithDates(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]Build, Pagination, error) { 160 var newPage = Page{Limit: page.Limit} 161 162 tx, err := conn.Begin() 163 if err != nil { 164 return nil, Pagination{}, err 165 } 166 167 defer Rollback(tx) 168 169 if page.From != nil { 170 fromRow, err := buildsQuery. 171 Where(sq.Expr("b.start_time >= to_timestamp(" + strconv.Itoa(*page.From) + ")")). 172 OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC"). 173 Limit(1). 174 RunWith(tx). 175 Query() 176 177 if err != nil { 178 // The user has no builds since that given time 179 if err == sql.ErrNoRows { 180 return []Build{}, Pagination{}, nil 181 } 182 183 return nil, Pagination{}, err 184 } 185 186 defer fromRow.Close() 187 188 found := false 189 for fromRow.Next() { 190 found = true 191 build := newEmptyBuild(conn, lockFactory) 192 err = scanBuild(build, fromRow, conn.EncryptionStrategy()) 193 if err != nil { 194 return nil, Pagination{}, err 195 } 196 197 newPage.From = NewIntPtr(build.ID()) 198 } 199 if !found { 200 return []Build{}, Pagination{}, nil 201 } 202 } 203 204 if page.To != nil { 205 untilRow, err := buildsQuery. 206 Where(sq.Expr("b.start_time <= to_timestamp(" + strconv.Itoa(*page.To) + ")")). 207 OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC"). 208 Limit(1). 209 RunWith(tx). 210 Query() 211 if err != nil { 212 // The user has no builds since that given time 213 if err == sql.ErrNoRows { 214 return []Build{}, Pagination{}, nil 215 } 216 } 217 218 defer untilRow.Close() 219 220 found := false 221 for untilRow.Next() { 222 found = true 223 build := newEmptyBuild(conn, lockFactory) 224 err = scanBuild(build, untilRow, conn.EncryptionStrategy()) 225 if err != nil { 226 return nil, Pagination{}, err 227 } 228 229 newPage.To = NewIntPtr(build.ID()) 230 } 231 if !found { 232 return []Build{}, Pagination{}, nil 233 } 234 } 235 236 err = tx.Commit() 237 if err != nil { 238 return nil, Pagination{}, err 239 } 240 241 return getBuildsWithPagination(buildsQuery, minMaxIdQuery, newPage, conn, lockFactory) 242 } 243 244 func getBuildsWithPagination(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]Build, Pagination, error) { 245 var ( 246 rows *sql.Rows 247 err error 248 reverse bool 249 ) 250 251 origBuildsQuery := buildsQuery 252 253 tx, err := conn.Begin() 254 if err != nil { 255 return nil, Pagination{}, err 256 } 257 258 defer Rollback(tx) 259 260 buildsQuery = buildsQuery.Limit(uint64(page.Limit)) 261 262 if page.From == nil && page.To == nil { // none 263 buildsQuery = buildsQuery. 264 OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC") 265 } else if page.From != nil && page.To == nil { // only from 266 buildsQuery = buildsQuery. 267 Where(sq.GtOrEq{"b.id": uint64(*page.From)}). 268 OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC") 269 reverse = true 270 } else if page.From == nil && page.To != nil { // only to 271 buildsQuery = buildsQuery. 272 Where(sq.LtOrEq{"b.id": uint64(*page.To)}). 273 OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC") 274 } else if page.From != nil && page.To != nil { // both 275 if *page.From > *page.To { 276 return nil, Pagination{}, fmt.Errorf("Invalid range boundaries") 277 } 278 279 buildsQuery = buildsQuery.Where( 280 sq.And{ 281 sq.GtOrEq{"b.id": uint64(*page.From)}, 282 sq.LtOrEq{"b.id": uint64(*page.To)}, 283 }). 284 OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC") 285 } 286 287 rows, err = buildsQuery.RunWith(tx).Query() 288 if err != nil { 289 return nil, Pagination{}, err 290 } 291 292 defer Close(rows) 293 294 builds := make([]Build, 0) 295 for rows.Next() { 296 build := newEmptyBuild(conn, lockFactory) 297 err = scanBuild(build, rows, conn.EncryptionStrategy()) 298 if err != nil { 299 return nil, Pagination{}, err 300 } 301 302 builds = append(builds, build) 303 } 304 305 if reverse { 306 for i, j := 0, len(builds)-1; i < j; i, j = i+1, j-1 { 307 builds[i], builds[j] = builds[j], builds[i] 308 } 309 } 310 311 if len(builds) == 0 { 312 return builds, Pagination{}, nil 313 } 314 315 newestBuild := builds[0] 316 oldestBuild := builds[len(builds)-1] 317 318 var pagination Pagination 319 320 row := origBuildsQuery. 321 Where(sq.Lt{"b.id": oldestBuild.ID()}). 322 OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC"). 323 Limit(1). 324 RunWith(tx). 325 QueryRow() 326 327 build := newEmptyBuild(conn, lockFactory) 328 err = scanBuild(build, row, conn.EncryptionStrategy()) 329 if err != nil && err != sql.ErrNoRows { 330 return builds, Pagination{}, err 331 } else if err == nil { 332 pagination.Older = &Page{ 333 To: &build.id, 334 Limit: page.Limit, 335 } 336 } 337 338 row = origBuildsQuery. 339 Where(sq.Gt{"b.id": newestBuild.ID()}). 340 OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC"). 341 Limit(1). 342 RunWith(tx). 343 QueryRow() 344 345 build = newEmptyBuild(conn, lockFactory) 346 err = scanBuild(build, row, conn.EncryptionStrategy()) 347 if err != nil && err != sql.ErrNoRows { 348 return builds, Pagination{}, err 349 } else if err == nil { 350 pagination.Newer = &Page{ 351 From: &build.id, 352 Limit: page.Limit, 353 } 354 } 355 356 err = tx.Commit() 357 if err != nil { 358 return nil, Pagination{}, err 359 } 360 361 return builds, pagination, nil 362 }