1package db 2 3import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "strconv" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/concourse/concourse/atc" 11 "github.com/concourse/concourse/atc/db/lock" 12) 13 14type BaseResourceTypeNotFoundError struct { 15 Name string 16} 17 18func (e BaseResourceTypeNotFoundError) Error() string { 19 return fmt.Sprintf("base resource type not found: %s", e.Name) 20} 21 22var ErrResourceConfigAlreadyExists = errors.New("resource config already exists") 23var ErrResourceConfigDisappeared = errors.New("resource config disappeared") 24var ErrResourceConfigParentDisappeared = errors.New("resource config parent disappeared") 25var 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. 33type 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 46type ResourceConfig interface { 47 ID() int 48 CreatedByResourceCache() UsedResourceCache 49 CreatedByBaseResourceType() *UsedBaseResourceType 50 OriginBaseResourceType() *UsedBaseResourceType 51 52 FindResourceConfigScopeByID(int, Resource) (ResourceConfigScope, bool, error) 53} 54 55type resourceConfig struct { 56 id int 57 createdByResourceCache UsedResourceCache 58 createdByBaseResourceType *UsedBaseResourceType 59 lockFactory lock.LockFactory 60 conn Conn 61} 62 63func (r *resourceConfig) ID() int { return r.id } 64func (r *resourceConfig) CreatedByResourceCache() UsedResourceCache { return r.createdByResourceCache } 65func (r *resourceConfig) CreatedByBaseResourceType() *UsedBaseResourceType { 66 return r.createdByBaseResourceType 67} 68 69func (r *resourceConfig) OriginBaseResourceType() *UsedBaseResourceType { 70 if r.createdByBaseResourceType != nil { 71 return r.createdByBaseResourceType 72 } 73 return r.createdByResourceCache.ResourceConfig().OriginBaseResourceType() 74} 75 76func (r *resourceConfig) FindResourceConfigScopeByID(resourceConfigScopeID int, resource Resource) (ResourceConfigScope, bool, error) { 77 var ( 78 id int 79 rcID int 80 rID sql.NullString 81 checkErrBlob sql.NullString 82 ) 83 84 err := psql.Select("id, resource_id, resource_config_id, check_error"). 85 From("resource_config_scopes"). 86 Where(sq.Eq{ 87 "id": resourceConfigScopeID, 88 "resource_config_id": r.id, 89 }). 90 RunWith(r.conn). 91 QueryRow(). 92 Scan(&id, &rID, &rcID, &checkErrBlob) 93 if err != nil { 94 if err == sql.ErrNoRows { 95 return nil, false, nil 96 } 97 return nil, false, err 98 } 99 100 var uniqueResource Resource 101 if rID.Valid { 102 var resourceID int 103 resourceID, err = strconv.Atoi(rID.String) 104 if err != nil { 105 return nil, false, err 106 } 107 108 if resource.ID() == resourceID { 109 uniqueResource = resource 110 } 111 } 112 113 var checkErr error 114 if checkErrBlob.Valid { 115 checkErr = errors.New(checkErrBlob.String) 116 } 117 118 return &resourceConfigScope{ 119 id: id, 120 resource: uniqueResource, 121 resourceConfig: r, 122 checkError: checkErr, 123 conn: r.conn, 124 lockFactory: r.lockFactory}, true, nil 125} 126 127func (r *ResourceConfigDescriptor) findOrCreate(tx Tx, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, error) { 128 rc := &resourceConfig{ 129 lockFactory: lockFactory, 130 conn: conn, 131 } 132 133 var parentID int 134 var parentColumnName string 135 if r.CreatedByResourceCache != nil { 136 parentColumnName = "resource_cache_id" 137 138 resourceCache, err := r.CreatedByResourceCache.findOrCreate(tx, lockFactory, conn) 139 if err != nil { 140 return nil, err 141 } 142 143 parentID = resourceCache.ID() 144 145 rc.createdByResourceCache = resourceCache 146 } 147 148 if r.CreatedByBaseResourceType != nil { 149 parentColumnName = "base_resource_type_id" 150 151 var err error 152 var found bool 153 rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx) 154 if err != nil { 155 return nil, err 156 } 157 158 if !found { 159 return nil, BaseResourceTypeNotFoundError{Name: r.CreatedByBaseResourceType.Name} 160 } 161 162 parentID = rc.CreatedByBaseResourceType().ID 163 } 164 165 id, found, err := r.findWithParentID(tx, parentColumnName, parentID) 166 if err != nil { 167 return nil, err 168 } 169 170 if !found { 171 hash := mapHash(r.Source) 172 173 var err error 174 err = psql.Insert("resource_configs"). 175 Columns( 176 parentColumnName, 177 "source_hash", 178 ). 179 Values( 180 parentID, 181 hash, 182 ). 183 Suffix(` 184 ON CONFLICT (`+parentColumnName+`, source_hash) DO UPDATE SET 185 `+parentColumnName+` = ?, 186 source_hash = ? 187 RETURNING id 188 `, parentID, hash). 189 RunWith(tx). 190 QueryRow(). 191 Scan(&id) 192 193 if err != nil { 194 return nil, err 195 } 196 } 197 198 rc.id = id 199 200 return rc, nil 201} 202 203func (r *ResourceConfigDescriptor) find(tx Tx, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, bool, error) { 204 rc := &resourceConfig{ 205 lockFactory: lockFactory, 206 conn: conn, 207 } 208 209 var parentID int 210 var parentColumnName string 211 if r.CreatedByResourceCache != nil { 212 parentColumnName = "resource_cache_id" 213 214 resourceCache, found, err := r.CreatedByResourceCache.find(tx, lockFactory, conn) 215 if err != nil { 216 return nil, false, err 217 } 218 219 if !found { 220 return nil, false, nil 221 } 222 223 parentID = resourceCache.ID() 224 225 rc.createdByResourceCache = resourceCache 226 } 227 228 if r.CreatedByBaseResourceType != nil { 229 parentColumnName = "base_resource_type_id" 230 231 var err error 232 var found bool 233 rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx) 234 if err != nil { 235 return nil, false, err 236 } 237 238 if !found { 239 return nil, false, nil 240 } 241 242 parentID = rc.createdByBaseResourceType.ID 243 } 244 245 id, found, err := r.findWithParentID(tx, parentColumnName, parentID) 246 if err != nil { 247 return nil, false, err 248 } 249 250 if !found { 251 return nil, false, nil 252 } 253 254 rc.id = id 255 256 return rc, true, nil 257} 258 259func (r *ResourceConfigDescriptor) findWithParentID(tx Tx, parentColumnName string, parentID int) (int, bool, error) { 260 var id int 261 var whereClause sq.Eq 262 263 err := psql.Select("id"). 264 From("resource_configs"). 265 Where(sq.Eq{ 266 parentColumnName: parentID, 267 "source_hash": mapHash(r.Source), 268 }). 269 Where(whereClause). 270 Suffix("FOR SHARE"). 271 RunWith(tx). 272 QueryRow(). 273 Scan(&id) 274 if err != nil { 275 if err == sql.ErrNoRows { 276 return 0, false, nil 277 } 278 279 return 0, false, err 280 } 281 282 return id, true, nil 283} 284 285func findOrCreateResourceConfigScope( 286 tx Tx, 287 conn Conn, 288 lockFactory lock.LockFactory, 289 resourceConfig ResourceConfig, 290 resource Resource, 291 resourceType string, 292 resourceTypes atc.VersionedResourceTypes, 293) (ResourceConfigScope, error) { 294 295 var unique bool 296 var uniqueResource Resource 297 var resourceID *int 298 299 if resource != nil { 300 if !atc.EnableGlobalResources { 301 unique = true 302 } else { 303 customType, found := resourceTypes.Lookup(resourceType) 304 if found { 305 unique = customType.UniqueVersionHistory 306 } else { 307 baseType := resourceConfig.CreatedByBaseResourceType() 308 if baseType == nil { 309 return nil, ErrResourceConfigHasNoType 310 } 311 unique = baseType.UniqueVersionHistory 312 } 313 } 314 315 if unique { 316 id := resource.ID() 317 318 resourceID = &id 319 uniqueResource = resource 320 } 321 } 322 323 var scopeID int 324 var checkErr error 325 326 rows, err := psql.Select("id, check_error"). 327 From("resource_config_scopes"). 328 Where(sq.Eq{ 329 "resource_id": resourceID, 330 "resource_config_id": resourceConfig.ID(), 331 }). 332 RunWith(tx). 333 Query() 334 if err != nil { 335 return nil, err 336 } 337 338 if rows.Next() { 339 var checkErrBlob sql.NullString 340 341 err = rows.Scan(&scopeID, &checkErrBlob) 342 if err != nil { 343 return nil, err 344 } 345 346 if checkErrBlob.Valid { 347 checkErr = errors.New(checkErrBlob.String) 348 } 349 350 err = rows.Close() 351 if err != nil { 352 return nil, err 353 } 354 } else if unique && resource != nil { 355 // delete outdated scopes for resource 356 _, err := psql.Delete("resource_config_scopes"). 357 Where(sq.And{ 358 sq.Eq{ 359 "resource_id": resource.ID(), 360 }, 361 }). 362 RunWith(tx). 363 Exec() 364 if err != nil { 365 return nil, err 366 } 367 368 err = psql.Insert("resource_config_scopes"). 369 Columns("resource_id", "resource_config_id"). 370 Values(resource.ID(), resourceConfig.ID()). 371 Suffix(` 372 ON CONFLICT (resource_id, resource_config_id) WHERE resource_id IS NOT NULL DO UPDATE SET 373 resource_id = ?, 374 resource_config_id = ? 375 RETURNING id 376 `, resource.ID(), resourceConfig.ID()). 377 RunWith(tx). 378 QueryRow(). 379 Scan(&scopeID) 380 if err != nil { 381 return nil, err 382 } 383 } else { 384 err = psql.Insert("resource_config_scopes"). 385 Columns("resource_id", "resource_config_id"). 386 Values(nil, resourceConfig.ID()). 387 Suffix(` 388 ON CONFLICT (resource_config_id) WHERE resource_id IS NULL DO UPDATE SET 389 resource_config_id = ? 390 RETURNING id 391 `, resourceConfig.ID()). 392 RunWith(tx). 393 QueryRow(). 394 Scan(&scopeID) 395 if err != nil { 396 return nil, err 397 } 398 } 399 400 return &resourceConfigScope{ 401 id: scopeID, 402 resource: uniqueResource, 403 resourceConfig: resourceConfig, 404 checkError: checkErr, 405 conn: conn, 406 lockFactory: lockFactory, 407 }, nil 408} 409