github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_config_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"
    11  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    12  	"github.com/lib/pq"
    13  )
    14  
    15  type ErrCustomResourceTypeVersionNotFound struct {
    16  	Name string
    17  }
    18  
    19  func (e ErrCustomResourceTypeVersionNotFound) Error() string {
    20  	return fmt.Sprintf("custom resource type '%s' version not found", e.Name)
    21  }
    22  
    23  //go:generate counterfeiter . ResourceConfigFactory
    24  
    25  type ResourceConfigFactory interface {
    26  	FindOrCreateResourceConfig(
    27  		resourceType string,
    28  		source atc.Source,
    29  		resourceTypes atc.VersionedResourceTypes,
    30  	) (ResourceConfig, error)
    31  
    32  	FindResourceConfigByID(int) (ResourceConfig, bool, error)
    33  
    34  	CleanUnreferencedConfigs(time.Duration) error
    35  }
    36  
    37  type resourceConfigFactory struct {
    38  	conn        Conn
    39  	lockFactory lock.LockFactory
    40  }
    41  
    42  func NewResourceConfigFactory(conn Conn, lockFactory lock.LockFactory) ResourceConfigFactory {
    43  	return &resourceConfigFactory{
    44  		conn:        conn,
    45  		lockFactory: lockFactory,
    46  	}
    47  }
    48  
    49  func (f *resourceConfigFactory) FindResourceConfigByID(resourceConfigID int) (ResourceConfig, bool, error) {
    50  	tx, err := f.conn.Begin()
    51  	if err != nil {
    52  		return nil, false, err
    53  	}
    54  	defer Rollback(tx)
    55  
    56  	resourceConfig, found, err := findResourceConfigByID(tx, resourceConfigID, f.lockFactory, f.conn)
    57  	if err != nil {
    58  		return nil, false, err
    59  	}
    60  
    61  	if !found {
    62  		return nil, false, nil
    63  	}
    64  
    65  	err = tx.Commit()
    66  	if err != nil {
    67  		return nil, false, err
    68  	}
    69  
    70  	return resourceConfig, true, nil
    71  }
    72  
    73  func (f *resourceConfigFactory) FindOrCreateResourceConfig(
    74  	resourceType string,
    75  	source atc.Source,
    76  	resourceTypes atc.VersionedResourceTypes,
    77  ) (ResourceConfig, error) {
    78  	resourceConfigDescriptor, err := constructResourceConfigDescriptor(resourceType, source, resourceTypes)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	tx, err := f.conn.Begin()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer Rollback(tx)
    88  
    89  	resourceConfig, err := resourceConfigDescriptor.findOrCreate(tx, f.lockFactory, f.conn)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	err = resourceConfig.updateLastReferenced(tx)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	err = tx.Commit()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	return resourceConfig, nil
   105  }
   106  
   107  // constructResourceConfig cannot be called for constructing a resource type's
   108  // resource config while also containing the same resource type in the list of
   109  // resource types, because that results in a circular dependency.
   110  func constructResourceConfigDescriptor(
   111  	resourceTypeName string,
   112  	source atc.Source,
   113  	resourceTypes atc.VersionedResourceTypes,
   114  ) (ResourceConfigDescriptor, error) {
   115  	resourceConfigDescriptor := ResourceConfigDescriptor{
   116  		Source: source,
   117  	}
   118  
   119  	customType, found := resourceTypes.Lookup(resourceTypeName)
   120  	if found {
   121  		customTypeResourceConfig, err := constructResourceConfigDescriptor(
   122  			customType.Type,
   123  			customType.Source,
   124  			resourceTypes.Without(customType.Name),
   125  		)
   126  		if err != nil {
   127  			return ResourceConfigDescriptor{}, err
   128  		}
   129  
   130  		resourceConfigDescriptor.CreatedByResourceCache = &ResourceCacheDescriptor{
   131  			ResourceConfigDescriptor: customTypeResourceConfig,
   132  			Version:                  customType.Version,
   133  		}
   134  	} else {
   135  		resourceConfigDescriptor.CreatedByBaseResourceType = &BaseResourceType{
   136  			Name: resourceTypeName,
   137  		}
   138  	}
   139  
   140  	return resourceConfigDescriptor, nil
   141  }
   142  
   143  func (f *resourceConfigFactory) CleanUnreferencedConfigs(gracePeriod time.Duration) error {
   144  	usedByResourceCachesIds, _, err := sq.
   145  		Select("resource_config_id").
   146  		From("resource_caches").
   147  		ToSql()
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	usedByResourceIds, _, err := sq.
   153  		Select("resource_config_id").
   154  		From("resources").
   155  		Where("resource_config_id IS NOT NULL").
   156  		ToSql()
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	usedByResourceTypesIds, _, err := sq.
   162  		Select("resource_config_id").
   163  		From("resource_types").
   164  		Where("resource_config_id IS NOT NULL").
   165  		ToSql()
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	_, err = psql.Delete("resource_configs").
   171  		Where("id NOT IN (" + usedByResourceCachesIds + " UNION " + usedByResourceIds + " UNION " + usedByResourceTypesIds + ")").
   172  		Where(sq.Expr(fmt.Sprintf("now() - last_referenced > '%d seconds'::interval", int(gracePeriod.Seconds())))).
   173  		PlaceholderFormat(sq.Dollar).
   174  		RunWith(f.conn).Exec()
   175  	if err != nil {
   176  		if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqFKeyViolationErrCode {
   177  			// this can happen if a use or resource cache is created referencing the
   178  			// config; as the subqueries above are not atomic
   179  			return nil
   180  		}
   181  
   182  		return err
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func findResourceConfigByID(tx Tx, resourceConfigID int, lockFactory lock.LockFactory, conn Conn) (ResourceConfig, bool, error) {
   189  	var brtIDString, cacheIDString sql.NullString
   190  
   191  	err := psql.Select("base_resource_type_id", "resource_cache_id").
   192  		From("resource_configs").
   193  		Where(sq.Eq{"id": resourceConfigID}).
   194  		RunWith(tx).
   195  		QueryRow().
   196  		Scan(&brtIDString, &cacheIDString)
   197  	if err != nil {
   198  		if err == sql.ErrNoRows {
   199  			return nil, false, nil
   200  		}
   201  		return nil, false, err
   202  	}
   203  
   204  	rc := &resourceConfig{
   205  		id:          resourceConfigID,
   206  		lockFactory: lockFactory,
   207  		conn:        conn,
   208  	}
   209  
   210  	if brtIDString.Valid {
   211  		var brtName string
   212  		var unique bool
   213  		brtID, err := strconv.Atoi(brtIDString.String)
   214  		if err != nil {
   215  			return nil, false, err
   216  		}
   217  
   218  		err = psql.Select("name, unique_version_history").
   219  			From("base_resource_types").
   220  			Where(sq.Eq{"id": brtID}).
   221  			RunWith(tx).
   222  			QueryRow().
   223  			Scan(&brtName, &unique)
   224  		if err != nil {
   225  			return nil, false, err
   226  		}
   227  
   228  		rc.createdByBaseResourceType = &UsedBaseResourceType{brtID, brtName, unique}
   229  
   230  	} else if cacheIDString.Valid {
   231  		cacheID, err := strconv.Atoi(cacheIDString.String)
   232  		if err != nil {
   233  			return nil, false, err
   234  		}
   235  
   236  		usedByResourceCache, found, err := findResourceCacheByID(tx, cacheID, lockFactory, conn)
   237  		if err != nil {
   238  			return nil, false, err
   239  		}
   240  
   241  		if !found {
   242  			return nil, false, nil
   243  		}
   244  
   245  		rc.createdByResourceCache = usedByResourceCache
   246  	}
   247  
   248  	return rc, true, nil
   249  }