github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_config.go (about) 1 package db 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "time" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/pf-qiu/concourse/v6/atc" 11 "github.com/pf-qiu/concourse/v6/atc/db/lock" 12 ) 13 14 type BaseResourceTypeNotFoundError struct { 15 Name string 16 } 17 18 func (e BaseResourceTypeNotFoundError) Error() string { 19 return fmt.Sprintf("base resource type not found: %s", e.Name) 20 } 21 22 var ErrResourceConfigAlreadyExists = errors.New("resource config already exists") 23 var ErrResourceConfigDisappeared = errors.New("resource config disappeared") 24 var ErrResourceConfigParentDisappeared = errors.New("resource config parent disappeared") 25 var ErrResourceConfigHasNoType = errors.New("resource config has no type") 26 27 // ResourceConfig represents a resource type and config source. 28 // 29 // Resources in a pipeline, resource types in a pipeline, and `image_resource` 30 // fields in a task all result in a reference to a ResourceConfig. 31 // 32 // ResourceConfigs are garbage-collected by gc.ResourceConfigCollector. 33 type ResourceConfigDescriptor struct { 34 // A resource type provided by a resource. 35 CreatedByResourceCache *ResourceCacheDescriptor 36 37 // A resource type provided by a worker. 38 CreatedByBaseResourceType *BaseResourceType 39 40 // The resource's source configuration. 41 Source atc.Source 42 } 43 44 //go:generate counterfeiter . ResourceConfig 45 46 type ResourceConfig interface { 47 ID() int 48 LastReferenced() time.Time 49 CreatedByResourceCache() UsedResourceCache 50 CreatedByBaseResourceType() *UsedBaseResourceType 51 52 OriginBaseResourceType() *UsedBaseResourceType 53 54 FindOrCreateScope(Resource) (ResourceConfigScope, error) 55 } 56 57 type resourceConfig struct { 58 id int 59 lastReferenced time.Time 60 createdByResourceCache UsedResourceCache 61 createdByBaseResourceType *UsedBaseResourceType 62 lockFactory lock.LockFactory 63 conn Conn 64 } 65 66 func (r *resourceConfig) ID() int { 67 return r.id 68 } 69 70 func (r *resourceConfig) LastReferenced() time.Time { 71 return r.lastReferenced 72 } 73 74 func (r *resourceConfig) CreatedByResourceCache() UsedResourceCache { 75 return r.createdByResourceCache 76 } 77 78 func (r *resourceConfig) CreatedByBaseResourceType() *UsedBaseResourceType { 79 return r.createdByBaseResourceType 80 } 81 82 func (r *resourceConfig) OriginBaseResourceType() *UsedBaseResourceType { 83 if r.createdByBaseResourceType != nil { 84 return r.createdByBaseResourceType 85 } 86 return r.createdByResourceCache.ResourceConfig().OriginBaseResourceType() 87 } 88 89 func (r *resourceConfig) FindOrCreateScope(resource Resource) (ResourceConfigScope, error) { 90 tx, err := r.conn.Begin() 91 if err != nil { 92 return nil, err 93 } 94 95 defer Rollback(tx) 96 97 scope, err := findOrCreateResourceConfigScope( 98 tx, 99 r.conn, 100 r.lockFactory, 101 r, 102 resource, 103 ) 104 if err != nil { 105 return nil, err 106 } 107 108 err = tx.Commit() 109 if err != nil { 110 return nil, err 111 } 112 113 return scope, nil 114 } 115 116 func (r *resourceConfig) updateLastReferenced(tx Tx) error { 117 return psql.Update("resource_configs"). 118 Set("last_referenced", sq.Expr("now()")). 119 Where(sq.Eq{"id": r.id}). 120 Suffix("RETURNING last_referenced"). 121 RunWith(tx). 122 QueryRow(). 123 Scan(&r.lastReferenced) 124 } 125 126 func (r *ResourceConfigDescriptor) findOrCreate(tx Tx, lockFactory lock.LockFactory, conn Conn) (*resourceConfig, error) { 127 rc := &resourceConfig{ 128 lockFactory: lockFactory, 129 conn: conn, 130 } 131 132 var parentID int 133 var parentColumnName string 134 if r.CreatedByResourceCache != nil { 135 parentColumnName = "resource_cache_id" 136 137 resourceCache, err := r.CreatedByResourceCache.findOrCreate(tx, lockFactory, conn) 138 if err != nil { 139 return nil, err 140 } 141 142 parentID = resourceCache.ID() 143 144 rc.createdByResourceCache = resourceCache 145 } 146 147 if r.CreatedByBaseResourceType != nil { 148 parentColumnName = "base_resource_type_id" 149 150 var err error 151 var found bool 152 rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx) 153 if err != nil { 154 return nil, err 155 } 156 157 if !found { 158 return nil, BaseResourceTypeNotFoundError{Name: r.CreatedByBaseResourceType.Name} 159 } 160 161 parentID = rc.CreatedByBaseResourceType().ID 162 } 163 164 found, err := r.findWithParentID(tx, rc, parentColumnName, parentID) 165 if err != nil { 166 return nil, err 167 } 168 169 if !found { 170 hash := mapHash(r.Source) 171 172 var err error 173 err = psql.Insert("resource_configs"). 174 Columns( 175 parentColumnName, 176 "source_hash", 177 ). 178 Values( 179 parentID, 180 hash, 181 ). 182 Suffix(` 183 ON CONFLICT (`+parentColumnName+`, source_hash) DO UPDATE SET 184 `+parentColumnName+` = ?, 185 source_hash = ? 186 RETURNING id, last_referenced 187 `, parentID, hash). 188 RunWith(tx). 189 QueryRow(). 190 Scan(&rc.id, &rc.lastReferenced) 191 if err != nil { 192 return nil, err 193 } 194 } 195 196 return rc, nil 197 } 198 199 func (r *ResourceConfigDescriptor) find(tx Tx, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, bool, error) { 200 rc := &resourceConfig{ 201 lockFactory: lockFactory, 202 conn: conn, 203 } 204 205 var parentID int 206 var parentColumnName string 207 if r.CreatedByResourceCache != nil { 208 parentColumnName = "resource_cache_id" 209 210 resourceCache, found, err := r.CreatedByResourceCache.find(tx, lockFactory, conn) 211 if err != nil { 212 return nil, false, err 213 } 214 215 if !found { 216 return nil, false, nil 217 } 218 219 parentID = resourceCache.ID() 220 221 rc.createdByResourceCache = resourceCache 222 } 223 224 if r.CreatedByBaseResourceType != nil { 225 parentColumnName = "base_resource_type_id" 226 227 var err error 228 var found bool 229 rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx) 230 if err != nil { 231 return nil, false, err 232 } 233 234 if !found { 235 return nil, false, nil 236 } 237 238 parentID = rc.createdByBaseResourceType.ID 239 } 240 241 found, err := r.findWithParentID(tx, rc, parentColumnName, parentID) 242 if err != nil { 243 return nil, false, err 244 } 245 246 if !found { 247 return nil, false, nil 248 } 249 250 return rc, true, nil 251 } 252 253 func (r *ResourceConfigDescriptor) findWithParentID(tx Tx, rc *resourceConfig, parentColumnName string, parentID int) (bool, error) { 254 err := psql.Select("id", "last_referenced"). 255 From("resource_configs"). 256 Where(sq.Eq{ 257 parentColumnName: parentID, 258 "source_hash": mapHash(r.Source), 259 }). 260 Suffix("FOR UPDATE"). 261 RunWith(tx). 262 QueryRow(). 263 Scan(&rc.id, &rc.lastReferenced) 264 if err != nil { 265 if err == sql.ErrNoRows { 266 return false, nil 267 } 268 269 return false, err 270 } 271 272 return true, nil 273 } 274 275 func findOrCreateResourceConfigScope( 276 tx Tx, 277 conn Conn, 278 lockFactory lock.LockFactory, 279 resourceConfig ResourceConfig, 280 resource Resource, 281 ) (ResourceConfigScope, error) { 282 var uniqueResource Resource 283 var resourceID *int 284 285 if resource != nil { 286 var unique bool 287 if !atc.EnableGlobalResources { 288 unique = true 289 } else { 290 if brt := resourceConfig.CreatedByBaseResourceType(); brt != nil { 291 unique = brt.UniqueVersionHistory 292 } 293 } 294 295 if unique { 296 id := resource.ID() 297 298 resourceID = &id 299 uniqueResource = resource 300 } 301 } 302 303 var scopeID int 304 305 rows, err := psql.Select("id"). 306 From("resource_config_scopes"). 307 Where(sq.Eq{ 308 "resource_id": resourceID, 309 "resource_config_id": resourceConfig.ID(), 310 }). 311 RunWith(tx). 312 Query() 313 if err != nil { 314 return nil, err 315 } 316 317 if rows.Next() { 318 err = rows.Scan(&scopeID) 319 if err != nil { 320 return nil, err 321 } 322 323 err = rows.Close() 324 if err != nil { 325 return nil, err 326 } 327 } else if uniqueResource != nil { 328 // delete outdated scopes for resource 329 _, err := psql.Delete("resource_config_scopes"). 330 Where(sq.And{ 331 sq.Eq{ 332 "resource_id": resource.ID(), 333 }, 334 }). 335 RunWith(tx). 336 Exec() 337 if err != nil { 338 return nil, err 339 } 340 341 err = psql.Insert("resource_config_scopes"). 342 Columns("resource_id", "resource_config_id"). 343 Values(resource.ID(), resourceConfig.ID()). 344 Suffix(` 345 ON CONFLICT (resource_id, resource_config_id) WHERE resource_id IS NOT NULL DO UPDATE SET 346 resource_id = ?, 347 resource_config_id = ? 348 RETURNING id 349 `, resource.ID(), resourceConfig.ID()). 350 RunWith(tx). 351 QueryRow(). 352 Scan(&scopeID) 353 if err != nil { 354 return nil, err 355 } 356 } else { 357 err = psql.Insert("resource_config_scopes"). 358 Columns("resource_id", "resource_config_id"). 359 Values(nil, resourceConfig.ID()). 360 Suffix(` 361 ON CONFLICT (resource_config_id) WHERE resource_id IS NULL DO UPDATE SET 362 resource_config_id = ? 363 RETURNING id 364 `, resourceConfig.ID()). 365 RunWith(tx). 366 QueryRow(). 367 Scan(&scopeID) 368 if err != nil { 369 return nil, err 370 } 371 } 372 373 return &resourceConfigScope{ 374 id: scopeID, 375 resource: uniqueResource, 376 resourceConfig: resourceConfig, 377 conn: conn, 378 lockFactory: lockFactory, 379 }, nil 380 }