github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_type.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	sq "github.com/Masterminds/squirrel"
    12  	"github.com/pf-qiu/concourse/v6/atc"
    13  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    14  	"github.com/lib/pq"
    15  )
    16  
    17  type ResourceTypeNotFoundError struct {
    18  	ID int
    19  }
    20  
    21  func (e ResourceTypeNotFoundError) Error() string {
    22  	return fmt.Sprintf("resource type not found: %d", e.ID)
    23  }
    24  
    25  //go:generate counterfeiter . ResourceType
    26  
    27  type ResourceType interface {
    28  	PipelineRef
    29  
    30  	ID() int
    31  	TeamID() int
    32  	TeamName() string
    33  	Name() string
    34  	Type() string
    35  	Privileged() bool
    36  	Source() atc.Source
    37  	Defaults() atc.Source
    38  	Params() atc.Params
    39  	Tags() atc.Tags
    40  	CheckEvery() string
    41  	CheckTimeout() string
    42  	LastCheckStartTime() time.Time
    43  	LastCheckEndTime() time.Time
    44  	CurrentPinnedVersion() atc.Version
    45  	ResourceConfigScopeID() int
    46  
    47  	HasWebhook() bool
    48  
    49  	SetResourceConfigScope(ResourceConfigScope) error
    50  
    51  	CheckPlan(atc.Version, time.Duration, ResourceTypes, atc.Source) atc.CheckPlan
    52  	CreateBuild(context.Context, bool, atc.Plan) (Build, bool, error)
    53  
    54  	Version() atc.Version
    55  
    56  	Reload() (bool, error)
    57  }
    58  
    59  type ResourceTypes []ResourceType
    60  
    61  func (resourceTypes ResourceTypes) Parent(checkable Checkable) (ResourceType, bool) {
    62  	for _, t := range resourceTypes {
    63  		if t.PipelineID() == checkable.PipelineID() {
    64  			if t != checkable && t.Name() == checkable.Type() {
    65  				return t, true
    66  			}
    67  		}
    68  	}
    69  	return nil, false
    70  }
    71  
    72  func (resourceTypes ResourceTypes) Filter(checkable Checkable) ResourceTypes {
    73  	var result ResourceTypes
    74  
    75  	for {
    76  		resourceType, found := resourceTypes.Parent(checkable)
    77  		if !found {
    78  			return result
    79  		}
    80  
    81  		result = append(result, resourceType)
    82  		checkable = resourceType
    83  	}
    84  }
    85  
    86  func (resourceTypes ResourceTypes) Deserialize() atc.VersionedResourceTypes {
    87  	var versionedResourceTypes atc.VersionedResourceTypes
    88  
    89  	for _, t := range resourceTypes {
    90  		// Apply source defaults to resource types
    91  		source := t.Source()
    92  		parentType, found := resourceTypes.Parent(t)
    93  		if found {
    94  			source = parentType.Defaults().Merge(source)
    95  		} else {
    96  			defaults, found := atc.FindBaseResourceTypeDefaults(t.Type())
    97  			if found {
    98  				source = defaults.Merge(source)
    99  			}
   100  		}
   101  
   102  		versionedResourceTypes = append(versionedResourceTypes, atc.VersionedResourceType{
   103  			ResourceType: atc.ResourceType{
   104  				Name:       t.Name(),
   105  				Type:       t.Type(),
   106  				Source:     source,
   107  				Defaults:   t.Defaults(),
   108  				Privileged: t.Privileged(),
   109  				CheckEvery: t.CheckEvery(),
   110  				Tags:       t.Tags(),
   111  				Params:     t.Params(),
   112  			},
   113  			Version: t.Version(),
   114  		})
   115  	}
   116  
   117  	return versionedResourceTypes
   118  }
   119  
   120  func (resourceTypes ResourceTypes) Configs() atc.ResourceTypes {
   121  	var configs atc.ResourceTypes
   122  
   123  	for _, r := range resourceTypes {
   124  		configs = append(configs, atc.ResourceType{
   125  			Name:       r.Name(),
   126  			Type:       r.Type(),
   127  			Source:     r.Source(),
   128  			Defaults:   r.Defaults(),
   129  			Privileged: r.Privileged(),
   130  			CheckEvery: r.CheckEvery(),
   131  			Tags:       r.Tags(),
   132  			Params:     r.Params(),
   133  		})
   134  	}
   135  
   136  	return configs
   137  }
   138  
   139  var resourceTypesQuery = psql.Select(
   140  	"r.id",
   141  	"r.pipeline_id",
   142  	"r.name",
   143  	"r.type",
   144  	"r.config",
   145  	"rcv.version",
   146  	"r.nonce",
   147  	"p.name",
   148  	"p.instance_vars",
   149  	"t.id",
   150  	"t.name",
   151  	"ro.id",
   152  	"ro.last_check_start_time",
   153  	"ro.last_check_end_time",
   154  ).
   155  	From("resource_types r").
   156  	Join("pipelines p ON p.id = r.pipeline_id").
   157  	Join("teams t ON t.id = p.team_id").
   158  	LeftJoin("resource_configs c ON c.id = r.resource_config_id").
   159  	LeftJoin("resource_config_scopes ro ON ro.resource_config_id = c.id").
   160  	LeftJoin(`LATERAL (
   161  		SELECT rcv.*
   162  		FROM resource_config_versions rcv
   163  		WHERE rcv.resource_config_scope_id = ro.id
   164  		ORDER BY rcv.check_order DESC
   165  		LIMIT 1
   166  	) AS rcv ON true`).
   167  	Where(sq.Eq{"r.active": true})
   168  
   169  type resourceType struct {
   170  	pipelineRef
   171  
   172  	id                    int
   173  	teamID                int
   174  	resourceConfigScopeID int
   175  	teamName              string
   176  	name                  string
   177  	type_                 string
   178  	privileged            bool
   179  	source                atc.Source
   180  	defaults              atc.Source
   181  	params                atc.Params
   182  	tags                  atc.Tags
   183  	version               atc.Version
   184  	checkEvery            string
   185  	lastCheckStartTime    time.Time
   186  	lastCheckEndTime      time.Time
   187  }
   188  
   189  func (t *resourceType) ID() int                       { return t.id }
   190  func (t *resourceType) TeamID() int                   { return t.teamID }
   191  func (t *resourceType) TeamName() string              { return t.teamName }
   192  func (t *resourceType) Name() string                  { return t.name }
   193  func (t *resourceType) Type() string                  { return t.type_ }
   194  func (t *resourceType) Privileged() bool              { return t.privileged }
   195  func (t *resourceType) CheckEvery() string            { return t.checkEvery }
   196  func (t *resourceType) CheckTimeout() string          { return "" }
   197  func (r *resourceType) LastCheckStartTime() time.Time { return r.lastCheckStartTime }
   198  func (r *resourceType) LastCheckEndTime() time.Time   { return r.lastCheckEndTime }
   199  func (t *resourceType) Source() atc.Source            { return t.source }
   200  func (t *resourceType) Defaults() atc.Source          { return t.defaults }
   201  func (t *resourceType) Params() atc.Params            { return t.params }
   202  func (t *resourceType) Tags() atc.Tags                { return t.tags }
   203  func (t *resourceType) ResourceConfigScopeID() int    { return t.resourceConfigScopeID }
   204  
   205  func (t *resourceType) Version() atc.Version              { return t.version }
   206  func (t *resourceType) CurrentPinnedVersion() atc.Version { return nil }
   207  
   208  func (t *resourceType) HasWebhook() bool {
   209  	return false
   210  }
   211  
   212  func newEmptyResourceType(conn Conn, lockFactory lock.LockFactory) *resourceType {
   213  	return &resourceType{pipelineRef: pipelineRef{conn: conn, lockFactory: lockFactory}}
   214  }
   215  
   216  func (t *resourceType) Reload() (bool, error) {
   217  	row := resourceTypesQuery.Where(sq.Eq{"r.id": t.id}).RunWith(t.conn).QueryRow()
   218  
   219  	err := scanResourceType(t, row)
   220  	if err != nil {
   221  		if err == sql.ErrNoRows {
   222  			return false, nil
   223  		}
   224  		return false, err
   225  	}
   226  
   227  	return true, nil
   228  }
   229  
   230  func (r *resourceType) SetResourceConfig(atc.Source, atc.VersionedResourceTypes) (ResourceConfigScope, error) {
   231  	return nil, fmt.Errorf("not implemented")
   232  }
   233  
   234  func (r *resourceType) SetResourceConfigScope(scope ResourceConfigScope) error {
   235  	_, err := psql.Update("resource_types").
   236  		Set("resource_config_id", scope.ResourceConfig().ID()).
   237  		Where(sq.Eq{"id": r.id}).
   238  		Where(sq.Or{
   239  			sq.Eq{"resource_config_id": nil},
   240  			sq.NotEq{"resource_config_id": scope.ResourceConfig().ID()},
   241  		}).
   242  		RunWith(r.conn).
   243  		Exec()
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	return nil
   249  }
   250  
   251  func (r *resourceType) CheckPlan(from atc.Version, interval time.Duration, resourceTypes ResourceTypes, sourceDefaults atc.Source) atc.CheckPlan {
   252  	return atc.CheckPlan{
   253  		Name:   r.Name(),
   254  		Type:   r.Type(),
   255  		Source: sourceDefaults.Merge(r.Source()),
   256  		Tags:   r.Tags(),
   257  
   258  		FromVersion:            from,
   259  		Interval:               interval.String(),
   260  		VersionedResourceTypes: resourceTypes.Deserialize(),
   261  
   262  		ResourceType: r.Name(),
   263  	}
   264  }
   265  
   266  func (r *resourceType) CreateBuild(ctx context.Context, manuallyTriggered bool, plan atc.Plan) (Build, bool, error) {
   267  	spanContextJSON, err := json.Marshal(NewSpanContext(ctx))
   268  	if err != nil {
   269  		return nil, false, err
   270  	}
   271  
   272  	tx, err := r.conn.Begin()
   273  	if err != nil {
   274  		return nil, false, err
   275  	}
   276  
   277  	defer Rollback(tx)
   278  
   279  	if !manuallyTriggered {
   280  		var buildID int
   281  		var completed, noBuild bool
   282  		err = psql.Select("id", "completed").
   283  			From("builds").
   284  			Where(sq.Eq{"resource_type_id": r.id}).
   285  			RunWith(tx).
   286  			QueryRow().
   287  			Scan(&buildID, &completed)
   288  		if err != nil {
   289  			if err == sql.ErrNoRows {
   290  				noBuild = true
   291  			} else {
   292  				return nil, false, err
   293  			}
   294  		}
   295  
   296  		if !noBuild && !completed {
   297  			// a build is already running; leave it be
   298  			return nil, false, nil
   299  		}
   300  
   301  		if completed {
   302  			// previous build finished; clear it out
   303  			_, err = psql.Delete("builds").
   304  				Where(sq.Eq{
   305  					"resource_type_id": r.id,
   306  					"completed":        true,
   307  				}).
   308  				RunWith(tx).
   309  				Exec()
   310  			if err != nil {
   311  				return nil, false, fmt.Errorf("delete previous build: %w", err)
   312  			}
   313  			_, err = psql.Delete("build_events").
   314  				Where(sq.Eq{
   315  					"build_id": buildID,
   316  				}).
   317  				RunWith(tx).
   318  				Exec()
   319  			if err != nil {
   320  				return nil, false, fmt.Errorf("delete previous build events: %w", err)
   321  			}
   322  		}
   323  	}
   324  
   325  	build := newEmptyBuild(r.conn, r.lockFactory)
   326  	err = createBuild(tx, build, map[string]interface{}{
   327  		"name":               CheckBuildName,
   328  		"team_id":            r.teamID,
   329  		"pipeline_id":        r.pipelineID,
   330  		"resource_type_id":   r.id,
   331  		"status":             BuildStatusPending,
   332  		"manually_triggered": manuallyTriggered,
   333  		"span_context":       string(spanContextJSON),
   334  	})
   335  	if err != nil {
   336  		return nil, false, err
   337  	}
   338  
   339  	_, err = build.start(tx, plan)
   340  	if err != nil {
   341  		return nil, false, err
   342  	}
   343  
   344  	err = tx.Commit()
   345  	if err != nil {
   346  		return nil, false, err
   347  	}
   348  
   349  	err = r.conn.Bus().Notify(atc.ComponentBuildTracker)
   350  	if err != nil {
   351  		return nil, false, err
   352  	}
   353  
   354  	_, err = build.Reload()
   355  	if err != nil {
   356  		return nil, false, err
   357  	}
   358  
   359  	return build, true, nil
   360  }
   361  
   362  func scanResourceType(t *resourceType, row scannable) error {
   363  	var (
   364  		configJSON                           sql.NullString
   365  		rcsID, version, nonce                sql.NullString
   366  		lastCheckStartTime, lastCheckEndTime pq.NullTime
   367  		pipelineInstanceVars                 sql.NullString
   368  	)
   369  
   370  	err := row.Scan(&t.id, &t.pipelineID, &t.name, &t.type_, &configJSON, &version, &nonce, &t.pipelineName, &pipelineInstanceVars, &t.teamID, &t.teamName, &rcsID, &lastCheckStartTime, &lastCheckEndTime)
   371  	if err != nil {
   372  		return err
   373  	}
   374  
   375  	t.lastCheckStartTime = lastCheckStartTime.Time
   376  	t.lastCheckEndTime = lastCheckEndTime.Time
   377  
   378  	if version.Valid {
   379  		err = json.Unmarshal([]byte(version.String), &t.version)
   380  		if err != nil {
   381  			return err
   382  		}
   383  	}
   384  
   385  	es := t.conn.EncryptionStrategy()
   386  
   387  	var noncense *string
   388  	if nonce.Valid {
   389  		noncense = &nonce.String
   390  	}
   391  
   392  	var config atc.ResourceType
   393  	if configJSON.Valid {
   394  		decryptedConfig, err := es.Decrypt(configJSON.String, noncense)
   395  		if err != nil {
   396  			return err
   397  		}
   398  
   399  		err = json.Unmarshal(decryptedConfig, &config)
   400  		if err != nil {
   401  			return err
   402  		}
   403  	} else {
   404  		config = atc.ResourceType{}
   405  	}
   406  
   407  	t.source = config.Source
   408  	t.defaults = config.Defaults
   409  	t.params = config.Params
   410  	t.privileged = config.Privileged
   411  	t.tags = config.Tags
   412  	t.checkEvery = config.CheckEvery
   413  
   414  	if rcsID.Valid {
   415  		t.resourceConfigScopeID, err = strconv.Atoi(rcsID.String)
   416  		if err != nil {
   417  			return err
   418  		}
   419  	}
   420  
   421  	if pipelineInstanceVars.Valid {
   422  		err = json.Unmarshal([]byte(pipelineInstanceVars.String), &t.pipelineInstanceVars)
   423  		if err != nil {
   424  			return err
   425  		}
   426  	}
   427  
   428  	return nil
   429  }