concourse/atc/db/resource_config.go

394 lines
9.0 KiB
Go

package db
import (
"database/sql"
"errors"
"fmt"
"strconv"
sq "github.com/Masterminds/squirrel"
"github.com/concourse/concourse/atc"
"github.com/concourse/concourse/atc/db/lock"
)
type BaseResourceTypeNotFoundError struct {
Name string
}
func (e BaseResourceTypeNotFoundError) Error() string {
return fmt.Sprintf("base resource type not found: %s", e.Name)
}
var ErrResourceConfigAlreadyExists = errors.New("resource config already exists")
var ErrResourceConfigDisappeared = errors.New("resource config disappeared")
var ErrResourceConfigParentDisappeared = errors.New("resource config parent disappeared")
// ResourceConfig represents a resource type and config source.
//
// Resources in a pipeline, resource types in a pipeline, and `image_resource`
// fields in a task all result in a reference to a ResourceConfig.
//
// ResourceConfigs are garbage-collected by gc.ResourceConfigCollector.
type ResourceConfigDescriptor struct {
// A resource type provided by a resource.
CreatedByResourceCache *ResourceCacheDescriptor
// A resource type provided by a worker.
CreatedByBaseResourceType *BaseResourceType
// The resource's source configuration.
Source atc.Source
}
//go:generate counterfeiter . ResourceConfig
type ResourceConfig interface {
ID() int
CreatedByResourceCache() UsedResourceCache
CreatedByBaseResourceType() *UsedBaseResourceType
OriginBaseResourceType() *UsedBaseResourceType
FindResourceConfigScopeByID(int, Resource) (ResourceConfigScope, bool, error)
}
type resourceConfig struct {
id int
createdByResourceCache UsedResourceCache
createdByBaseResourceType *UsedBaseResourceType
lockFactory lock.LockFactory
conn Conn
}
func (r *resourceConfig) ID() int { return r.id }
func (r *resourceConfig) CreatedByResourceCache() UsedResourceCache { return r.createdByResourceCache }
func (r *resourceConfig) CreatedByBaseResourceType() *UsedBaseResourceType {
return r.createdByBaseResourceType
}
func (r *resourceConfig) OriginBaseResourceType() *UsedBaseResourceType {
if r.createdByBaseResourceType != nil {
return r.createdByBaseResourceType
}
return r.createdByResourceCache.ResourceConfig().OriginBaseResourceType()
}
func (r *resourceConfig) FindResourceConfigScopeByID(resourceConfigScopeID int, resource Resource) (ResourceConfigScope, bool, error) {
var (
id int
rcID int
rID sql.NullString
checkErrBlob sql.NullString
)
err := psql.Select("id, resource_id, resource_config_id, check_error").
From("resource_config_scopes").
Where(sq.Eq{
"id": resourceConfigScopeID,
"resource_config_id": r.id,
}).
RunWith(r.conn).
QueryRow().
Scan(&id, &rID, &rcID, &checkErrBlob)
if err != nil {
if err == sql.ErrNoRows {
return nil, false, nil
}
return nil, false, err
}
var uniqueResource Resource
if rID.Valid {
var resourceID int
resourceID, err = strconv.Atoi(rID.String)
if err != nil {
return nil, false, err
}
if resource.ID() == resourceID {
uniqueResource = resource
}
}
var checkErr error
if checkErrBlob.Valid {
checkErr = errors.New(checkErrBlob.String)
}
return &resourceConfigScope{
id: id,
resource: uniqueResource,
resourceConfig: r,
checkError: checkErr,
conn: r.conn,
lockFactory: r.lockFactory}, true, nil
}
func (r *ResourceConfigDescriptor) findOrCreate(tx Tx, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, error) {
rc := &resourceConfig{
lockFactory: lockFactory,
conn: conn,
}
var parentID int
var parentColumnName string
if r.CreatedByResourceCache != nil {
parentColumnName = "resource_cache_id"
resourceCache, err := r.CreatedByResourceCache.findOrCreate(tx, lockFactory, conn)
if err != nil {
return nil, err
}
parentID = resourceCache.ID()
rc.createdByResourceCache = resourceCache
}
if r.CreatedByBaseResourceType != nil {
parentColumnName = "base_resource_type_id"
var err error
var found bool
rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx)
if err != nil {
return nil, err
}
if !found {
return nil, BaseResourceTypeNotFoundError{Name: r.CreatedByBaseResourceType.Name}
}
parentID = rc.CreatedByBaseResourceType().ID
}
id, found, err := r.findWithParentID(tx, parentColumnName, parentID)
if err != nil {
return nil, err
}
if !found {
hash := mapHash(r.Source)
var err error
err = psql.Insert("resource_configs").
Columns(
parentColumnName,
"source_hash",
).
Values(
parentID,
hash,
).
Suffix(`
ON CONFLICT (`+parentColumnName+`, source_hash) DO UPDATE SET
`+parentColumnName+` = ?,
source_hash = ?
RETURNING id
`, parentID, hash).
RunWith(tx).
QueryRow().
Scan(&id)
if err != nil {
return nil, err
}
}
rc.id = id
return rc, nil
}
func (r *ResourceConfigDescriptor) find(tx Tx, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, bool, error) {
rc := &resourceConfig{
lockFactory: lockFactory,
conn: conn,
}
var parentID int
var parentColumnName string
if r.CreatedByResourceCache != nil {
parentColumnName = "resource_cache_id"
resourceCache, found, err := r.CreatedByResourceCache.find(tx, lockFactory, conn)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
parentID = resourceCache.ID()
rc.createdByResourceCache = resourceCache
}
if r.CreatedByBaseResourceType != nil {
parentColumnName = "base_resource_type_id"
var err error
var found bool
rc.createdByBaseResourceType, found, err = r.CreatedByBaseResourceType.Find(tx)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
parentID = rc.createdByBaseResourceType.ID
}
id, found, err := r.findWithParentID(tx, parentColumnName, parentID)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
rc.id = id
return rc, true, nil
}
func (r *ResourceConfigDescriptor) findWithParentID(tx Tx, parentColumnName string, parentID int) (int, bool, error) {
var id int
var whereClause sq.Eq
err := psql.Select("id").
From("resource_configs").
Where(sq.Eq{
parentColumnName: parentID,
"source_hash": mapHash(r.Source),
}).
Where(whereClause).
Suffix("FOR SHARE").
RunWith(tx).
QueryRow().
Scan(&id)
if err != nil {
if err == sql.ErrNoRows {
return 0, false, nil
}
return 0, false, err
}
return id, true, nil
}
func findOrCreateResourceConfigScope(tx Tx, conn Conn, lockFactory lock.LockFactory, resourceConfig ResourceConfig, resource Resource, resourceTypes atc.VersionedResourceTypes) (ResourceConfigScope, error) {
var unique bool
var uniqueResource Resource
var resourceID *int
if resource != nil {
if !atc.EnableGlobalResources {
unique = true
} else {
customType, found := resourceTypes.Lookup(resource.Type())
if found {
unique = customType.UniqueVersionHistory
} else {
unique = resourceConfig.CreatedByBaseResourceType().UniqueVersionHistory
}
}
if unique {
id := resource.ID()
resourceID = &id
uniqueResource = resource
}
}
var scopeID int
var checkErr error
rows, err := psql.Select("id, check_error").
From("resource_config_scopes").
Where(sq.Eq{
"resource_id": resourceID,
"resource_config_id": resourceConfig.ID(),
}).
RunWith(tx).
Query()
if err != nil {
return nil, err
}
if rows.Next() {
var checkErrBlob sql.NullString
err = rows.Scan(&scopeID, &checkErrBlob)
if err != nil {
return nil, err
}
if checkErrBlob.Valid {
checkErr = errors.New(checkErrBlob.String)
}
err = rows.Close()
if err != nil {
return nil, err
}
} else if unique && resource != nil {
// delete outdated scopes for resource
_, err := psql.Delete("resource_config_scopes").
Where(sq.And{
sq.Eq{
"resource_id": resource.ID(),
},
}).
RunWith(tx).
Exec()
if err != nil {
return nil, err
}
err = psql.Insert("resource_config_scopes").
Columns("resource_id", "resource_config_id").
Values(resource.ID(), resourceConfig.ID()).
Suffix(`
ON CONFLICT (resource_id, resource_config_id) WHERE resource_id IS NOT NULL DO UPDATE SET
resource_id = ?,
resource_config_id = ?
RETURNING id
`, resource.ID(), resourceConfig.ID()).
RunWith(tx).
QueryRow().
Scan(&scopeID)
if err != nil {
return nil, err
}
} else {
err = psql.Insert("resource_config_scopes").
Columns("resource_id", "resource_config_id").
Values(nil, resourceConfig.ID()).
Suffix(`
ON CONFLICT (resource_config_id) WHERE resource_id IS NULL DO UPDATE SET
resource_config_id = ?
RETURNING id
`, resourceConfig.ID()).
RunWith(tx).
QueryRow().
Scan(&scopeID)
if err != nil {
return nil, err
}
}
return &resourceConfigScope{
id: scopeID,
resource: uniqueResource,
resourceConfig: resourceConfig,
checkError: checkErr,
conn: conn,
lockFactory: lockFactory,
}, nil
}