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  }