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

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	sq "github.com/Masterminds/squirrel"
    13  	"github.com/lib/pq"
    14  
    15  	"github.com/pf-qiu/concourse/v6/atc"
    16  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    17  )
    18  
    19  var ErrPinnedThroughConfig = errors.New("resource is pinned through config")
    20  
    21  const CheckBuildName = "check"
    22  
    23  //go:generate counterfeiter . Resource
    24  
    25  type Resource interface {
    26  	PipelineRef
    27  
    28  	ID() int
    29  	Name() string
    30  	Public() bool
    31  	TeamID() int
    32  	TeamName() string
    33  	Type() string
    34  	Source() atc.Source
    35  	CheckEvery() string
    36  	CheckTimeout() string
    37  	LastCheckStartTime() time.Time
    38  	LastCheckEndTime() time.Time
    39  	Tags() atc.Tags
    40  	WebhookToken() string
    41  	Config() atc.ResourceConfig
    42  	ConfigPinnedVersion() atc.Version
    43  	APIPinnedVersion() atc.Version
    44  	PinComment() string
    45  	SetPinComment(string) error
    46  	ResourceConfigID() int
    47  	ResourceConfigScopeID() int
    48  	Icon() string
    49  
    50  	HasWebhook() bool
    51  
    52  	CurrentPinnedVersion() atc.Version
    53  
    54  	BuildSummary() *atc.BuildSummary
    55  
    56  	Versions(page Page, versionFilter atc.Version) ([]atc.ResourceVersion, Pagination, bool, error)
    57  	FindVersion(filter atc.Version) (ResourceConfigVersion, bool, error) // Only used in tests!!
    58  	UpdateMetadata(atc.Version, ResourceConfigMetadataFields) (bool, error)
    59  
    60  	EnableVersion(rcvID int) error
    61  	DisableVersion(rcvID int) error
    62  
    63  	PinVersion(rcvID int) (bool, error)
    64  	UnpinVersion() error
    65  
    66  	SetResourceConfigScope(ResourceConfigScope) error
    67  
    68  	CheckPlan(atc.Version, time.Duration, ResourceTypes, atc.Source) atc.CheckPlan
    69  	CreateBuild(context.Context, bool, atc.Plan) (Build, bool, error)
    70  
    71  	NotifyScan() error
    72  
    73  	Reload() (bool, error)
    74  }
    75  
    76  var resourcesQuery = psql.Select(
    77  	"r.id",
    78  	"r.name",
    79  	"r.type",
    80  	"r.config",
    81  	"rs.last_check_start_time",
    82  	"rs.last_check_end_time",
    83  	"r.pipeline_id",
    84  	"r.nonce",
    85  	"r.resource_config_id",
    86  	"r.resource_config_scope_id",
    87  	"p.name",
    88  	"p.instance_vars",
    89  	"t.id",
    90  	"t.name",
    91  	"rp.version",
    92  	"rp.comment_text",
    93  	"rp.config",
    94  	"b.id",
    95  	"b.name",
    96  	"b.status",
    97  	"b.start_time",
    98  	"b.end_time",
    99  ).
   100  	From("resources r").
   101  	Join("pipelines p ON p.id = r.pipeline_id").
   102  	Join("teams t ON t.id = p.team_id").
   103  	LeftJoin("builds b ON b.id = r.build_id").
   104  	LeftJoin("resource_config_scopes rs ON r.resource_config_scope_id = rs.id").
   105  	LeftJoin("resource_pins rp ON rp.resource_id = r.id").
   106  	Where(sq.Eq{"r.active": true})
   107  
   108  type resource struct {
   109  	pipelineRef
   110  
   111  	id                    int
   112  	name                  string
   113  	teamID                int
   114  	teamName              string
   115  	type_                 string
   116  	lastCheckStartTime    time.Time
   117  	lastCheckEndTime      time.Time
   118  	config                atc.ResourceConfig
   119  	configPinnedVersion   atc.Version
   120  	apiPinnedVersion      atc.Version
   121  	pinComment            string
   122  	resourceConfigID      int
   123  	resourceConfigScopeID int
   124  	buildSummary          *atc.BuildSummary
   125  }
   126  
   127  func newEmptyResource(conn Conn, lockFactory lock.LockFactory) *resource {
   128  	return &resource{pipelineRef: pipelineRef{conn: conn, lockFactory: lockFactory}}
   129  }
   130  
   131  type ResourceNotFoundError struct {
   132  	ID int
   133  }
   134  
   135  func (e ResourceNotFoundError) Error() string {
   136  	return fmt.Sprintf("resource '%d' not found", e.ID)
   137  }
   138  
   139  type Resources []Resource
   140  
   141  func (resources Resources) Lookup(name string) (Resource, bool) {
   142  	for _, resource := range resources {
   143  		if resource.Name() == name {
   144  			return resource, true
   145  		}
   146  	}
   147  
   148  	return nil, false
   149  }
   150  
   151  func (resources Resources) Configs() atc.ResourceConfigs {
   152  	var configs atc.ResourceConfigs
   153  	for _, r := range resources {
   154  		configs = append(configs, r.Config())
   155  	}
   156  	return configs
   157  }
   158  
   159  func (r *resource) ID() int                          { return r.id }
   160  func (r *resource) Name() string                     { return r.name }
   161  func (r *resource) Public() bool                     { return r.config.Public }
   162  func (r *resource) TeamID() int                      { return r.teamID }
   163  func (r *resource) TeamName() string                 { return r.teamName }
   164  func (r *resource) Type() string                     { return r.type_ }
   165  func (r *resource) Source() atc.Source               { return r.config.Source }
   166  func (r *resource) CheckEvery() string               { return r.config.CheckEvery }
   167  func (r *resource) CheckTimeout() string             { return r.config.CheckTimeout }
   168  func (r *resource) LastCheckStartTime() time.Time    { return r.lastCheckStartTime }
   169  func (r *resource) LastCheckEndTime() time.Time      { return r.lastCheckEndTime }
   170  func (r *resource) Tags() atc.Tags                   { return r.config.Tags }
   171  func (r *resource) WebhookToken() string             { return r.config.WebhookToken }
   172  func (r *resource) Config() atc.ResourceConfig       { return r.config }
   173  func (r *resource) ConfigPinnedVersion() atc.Version { return r.configPinnedVersion }
   174  func (r *resource) APIPinnedVersion() atc.Version    { return r.apiPinnedVersion }
   175  func (r *resource) PinComment() string               { return r.pinComment }
   176  func (r *resource) ResourceConfigID() int            { return r.resourceConfigID }
   177  func (r *resource) ResourceConfigScopeID() int       { return r.resourceConfigScopeID }
   178  func (r *resource) Icon() string                     { return r.config.Icon }
   179  
   180  func (r *resource) HasWebhook() bool { return r.WebhookToken() != "" }
   181  
   182  func (r *resource) Reload() (bool, error) {
   183  	row := resourcesQuery.Where(sq.Eq{"r.id": r.id}).
   184  		RunWith(r.conn).
   185  		QueryRow()
   186  
   187  	err := scanResource(r, row)
   188  	if err != nil {
   189  		if err == sql.ErrNoRows {
   190  			return false, nil
   191  		}
   192  		return false, err
   193  	}
   194  
   195  	return true, nil
   196  }
   197  
   198  func (r *resource) SetResourceConfig(atc.Source, atc.VersionedResourceTypes) (ResourceConfigScope, error) {
   199  	return nil, fmt.Errorf("not implemented")
   200  }
   201  
   202  func (r *resource) SetResourceConfigScope(scope ResourceConfigScope) error {
   203  	tx, err := r.conn.Begin()
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	defer Rollback(tx)
   209  
   210  	err = r.setResourceConfigScopeInTransaction(tx, scope)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	err = tx.Commit()
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	return nil
   221  }
   222  
   223  func (r *resource) setResourceConfigScopeInTransaction(tx Tx, scope ResourceConfigScope) error {
   224  	results, err := psql.Update("resources").
   225  		Set("resource_config_id", scope.ResourceConfig().ID()).
   226  		Set("resource_config_scope_id", scope.ID()).
   227  		Where(sq.Eq{"id": r.id}).
   228  		Where(sq.Or{
   229  			sq.Eq{"resource_config_id": nil},
   230  			sq.Eq{"resource_config_scope_id": nil},
   231  			sq.NotEq{"resource_config_id": scope.ResourceConfig().ID()},
   232  			sq.NotEq{"resource_config_scope_id": scope.ID()},
   233  		}).
   234  		RunWith(tx).
   235  		Exec()
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	rowsAffected, err := results.RowsAffected()
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	if rowsAffected > 0 {
   246  		err = requestScheduleForJobsUsingResource(tx, r.id)
   247  		if err != nil {
   248  			return err
   249  		}
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  func (r *resource) CheckPlan(from atc.Version, interval time.Duration, resourceTypes ResourceTypes, sourceDefaults atc.Source) atc.CheckPlan {
   256  	return atc.CheckPlan{
   257  		Name:    r.Name(),
   258  		Type:    r.Type(),
   259  		Source:  sourceDefaults.Merge(r.Source()),
   260  		Tags:    r.Tags(),
   261  		Timeout: r.CheckTimeout(),
   262  
   263  		FromVersion:            from,
   264  		Interval:               interval.String(),
   265  		VersionedResourceTypes: resourceTypes.Deserialize(),
   266  
   267  		Resource: r.Name(),
   268  	}
   269  }
   270  
   271  func (r *resource) CreateBuild(ctx context.Context, manuallyTriggered bool, plan atc.Plan) (Build, bool, error) {
   272  	spanContextJSON, err := json.Marshal(NewSpanContext(ctx))
   273  	if err != nil {
   274  		return nil, false, err
   275  	}
   276  
   277  	tx, err := r.conn.Begin()
   278  	if err != nil {
   279  		return nil, false, err
   280  	}
   281  
   282  	defer Rollback(tx)
   283  
   284  	if !manuallyTriggered {
   285  		var completed, noBuild bool
   286  		err = psql.Select("completed").
   287  			From("builds").
   288  			Where(sq.Eq{"resource_id": r.id}).
   289  			RunWith(tx).
   290  			QueryRow().
   291  			Scan(&completed)
   292  		if err != nil {
   293  			if err == sql.ErrNoRows {
   294  				noBuild = true
   295  			} else {
   296  				return nil, false, err
   297  			}
   298  		}
   299  
   300  		if !noBuild && !completed {
   301  			// a build is already running; leave it be
   302  			return nil, false, nil
   303  		}
   304  
   305  		if completed {
   306  			// previous build finished; clear it out
   307  			rows, err := psql.Delete("builds").
   308  				Where(sq.Eq{
   309  					"resource_id": r.id,
   310  					"completed":   true,
   311  				}).
   312  				Suffix("RETURNING id").
   313  				RunWith(tx).
   314  				Query()
   315  			if err != nil {
   316  				return nil, false, fmt.Errorf("delete previous build: %w", err)
   317  			}
   318  
   319  			deletedBuildIDs := []int{}
   320  			for rows.Next() {
   321  				var id int
   322  				err := rows.Scan(&id)
   323  				if err != nil {
   324  					return nil, false, fmt.Errorf("scan deleted build id: %w", err)
   325  				}
   326  
   327  				deletedBuildIDs = append(deletedBuildIDs, id)
   328  			}
   329  
   330  			_, err = psql.Delete("build_events").
   331  				Where(sq.Eq{
   332  					"build_id": deletedBuildIDs,
   333  				}).
   334  				RunWith(tx).
   335  				Exec()
   336  			if err != nil {
   337  				return nil, false, fmt.Errorf("delete previous build events: %w", err)
   338  			}
   339  		}
   340  	}
   341  
   342  	build := newEmptyBuild(r.conn, r.lockFactory)
   343  	err = createBuild(tx, build, map[string]interface{}{
   344  		"name":               CheckBuildName,
   345  		"pipeline_id":        r.pipelineID,
   346  		"team_id":            r.teamID,
   347  		"status":             BuildStatusPending,
   348  		"manually_triggered": manuallyTriggered,
   349  		"span_context":       string(spanContextJSON),
   350  		"resource_id":        r.id,
   351  	})
   352  	if err != nil {
   353  		return nil, false, err
   354  	}
   355  
   356  	_, err = build.start(tx, plan)
   357  	if err != nil {
   358  		return nil, false, err
   359  	}
   360  
   361  	_, err = psql.Update("resources").
   362  		Set("build_id", build.ID()).
   363  		Where(sq.Eq{"id": r.id}).
   364  		RunWith(tx).
   365  		Exec()
   366  	if err != nil {
   367  		return nil, false, err
   368  	}
   369  
   370  	err = tx.Commit()
   371  	if err != nil {
   372  		return nil, false, err
   373  	}
   374  
   375  	err = r.conn.Bus().Notify(atc.ComponentBuildTracker)
   376  	if err != nil {
   377  		return nil, false, err
   378  	}
   379  
   380  	_, err = build.Reload()
   381  	if err != nil {
   382  		return nil, false, err
   383  	}
   384  
   385  	return build, true, nil
   386  }
   387  
   388  func (r *resource) UpdateMetadata(version atc.Version, metadata ResourceConfigMetadataFields) (bool, error) {
   389  	versionJSON, err := json.Marshal(version)
   390  	if err != nil {
   391  		return false, err
   392  	}
   393  
   394  	metadataJSON, err := json.Marshal(metadata)
   395  	if err != nil {
   396  		return false, err
   397  	}
   398  
   399  	_, err = psql.Update("resource_config_versions").
   400  		Set("metadata", string(metadataJSON)).
   401  		Where(sq.Eq{
   402  			"resource_config_scope_id": r.ResourceConfigScopeID(),
   403  		}).
   404  		Where(sq.Expr(
   405  			"version_md5 = md5(?)", versionJSON,
   406  		)).
   407  		RunWith(r.conn).
   408  		Exec()
   409  
   410  	if err != nil {
   411  		if err == sql.ErrNoRows {
   412  			return false, nil
   413  		}
   414  		return false, err
   415  	}
   416  	return true, nil
   417  }
   418  
   419  // XXX: Deprecated, only used in tests
   420  func (r *resource) FindVersion(v atc.Version) (ResourceConfigVersion, bool, error) {
   421  	if r.resourceConfigScopeID == 0 {
   422  		return nil, false, nil
   423  	}
   424  
   425  	ver := &resourceConfigVersion{
   426  		conn: r.conn,
   427  	}
   428  
   429  	versionByte, err := json.Marshal(v)
   430  	if err != nil {
   431  		return nil, false, err
   432  	}
   433  
   434  	row := resourceConfigVersionQuery.
   435  		Where(sq.Eq{
   436  			"v.resource_config_scope_id": r.resourceConfigScopeID,
   437  		}).
   438  		Where(sq.Expr("v.version_md5 = md5(?)", versionByte)).
   439  		RunWith(r.conn).
   440  		QueryRow()
   441  
   442  	err = scanResourceConfigVersion(ver, row)
   443  	if err != nil {
   444  		if err == sql.ErrNoRows {
   445  			return nil, false, nil
   446  		}
   447  		return nil, false, err
   448  	}
   449  
   450  	return ver, true, nil
   451  }
   452  
   453  func (r *resource) SetPinComment(comment string) error {
   454  	_, err := psql.Update("resource_pins").
   455  		Set("comment_text", comment).
   456  		Where(sq.Eq{"resource_id": r.ID()}).
   457  		RunWith(r.conn).
   458  		Exec()
   459  
   460  	return err
   461  }
   462  
   463  func (r *resource) CurrentPinnedVersion() atc.Version {
   464  	if r.configPinnedVersion != nil {
   465  		return r.configPinnedVersion
   466  	} else if r.apiPinnedVersion != nil {
   467  		return r.apiPinnedVersion
   468  	}
   469  	return nil
   470  }
   471  
   472  func (r *resource) BuildSummary() *atc.BuildSummary {
   473  	return r.buildSummary
   474  }
   475  
   476  func (r *resource) Versions(page Page, versionFilter atc.Version) ([]atc.ResourceVersion, Pagination, bool, error) {
   477  	tx, err := r.conn.Begin()
   478  	if err != nil {
   479  		return nil, Pagination{}, false, err
   480  	}
   481  
   482  	defer Rollback(tx)
   483  
   484  	query := `
   485  		SELECT v.id, v.version, v.metadata, v.check_order,
   486  			NOT EXISTS (
   487  				SELECT 1
   488  				FROM resource_disabled_versions d
   489  				WHERE v.version_md5 = d.version_md5
   490  				AND r.resource_config_scope_id = v.resource_config_scope_id
   491  				AND r.id = d.resource_id
   492  			)
   493  		FROM resource_config_versions v, resources r
   494  		WHERE r.id = $1 AND r.resource_config_scope_id = v.resource_config_scope_id
   495  	`
   496  
   497  	filterJSON := "{}"
   498  	if len(versionFilter) != 0 {
   499  		filterBytes, err := json.Marshal(versionFilter)
   500  		if err != nil {
   501  			return nil, Pagination{}, false, err
   502  		}
   503  
   504  		filterJSON = string(filterBytes)
   505  	}
   506  
   507  	var rows *sql.Rows
   508  	if page.From != nil {
   509  		rows, err = tx.Query(fmt.Sprintf(`
   510  			SELECT sub.*
   511  				FROM (
   512  						%s
   513  					AND version @> $4
   514  					AND v.check_order >= (SELECT check_order FROM resource_config_versions WHERE id = $2)
   515  				ORDER BY v.check_order ASC
   516  				LIMIT $3
   517  			) sub
   518  			ORDER BY sub.check_order DESC
   519  		`, query), r.id, *page.From, page.Limit, filterJSON)
   520  		if err != nil {
   521  			return nil, Pagination{}, false, err
   522  		}
   523  	} else if page.To != nil {
   524  		rows, err = tx.Query(fmt.Sprintf(`
   525  			%s
   526  				AND version @> $4
   527  				AND v.check_order <= (SELECT check_order FROM resource_config_versions WHERE id = $2)
   528  			ORDER BY v.check_order DESC
   529  			LIMIT $3
   530  		`, query), r.id, *page.To, page.Limit, filterJSON)
   531  		if err != nil {
   532  			return nil, Pagination{}, false, err
   533  		}
   534  	} else {
   535  		rows, err = tx.Query(fmt.Sprintf(`
   536  			%s
   537  			AND version @> $3
   538  			ORDER BY v.check_order DESC
   539  			LIMIT $2
   540  		`, query), r.id, page.Limit, filterJSON)
   541  		if err != nil {
   542  			return nil, Pagination{}, false, err
   543  		}
   544  	}
   545  
   546  	defer Close(rows)
   547  
   548  	type rcvCheckOrder struct {
   549  		ResourceConfigVersionID int
   550  		CheckOrder              int
   551  	}
   552  
   553  	rvs := make([]atc.ResourceVersion, 0)
   554  	checkOrderRVs := make([]rcvCheckOrder, 0)
   555  	for rows.Next() {
   556  		var (
   557  			metadataBytes sql.NullString
   558  			versionBytes  string
   559  			checkOrder    int
   560  		)
   561  
   562  		rv := atc.ResourceVersion{}
   563  		err := rows.Scan(&rv.ID, &versionBytes, &metadataBytes, &checkOrder, &rv.Enabled)
   564  		if err != nil {
   565  			return nil, Pagination{}, false, err
   566  		}
   567  
   568  		err = json.Unmarshal([]byte(versionBytes), &rv.Version)
   569  		if err != nil {
   570  			return nil, Pagination{}, false, err
   571  		}
   572  
   573  		if metadataBytes.Valid {
   574  			err = json.Unmarshal([]byte(metadataBytes.String), &rv.Metadata)
   575  			if err != nil {
   576  				return nil, Pagination{}, false, err
   577  			}
   578  		}
   579  
   580  		checkOrderRV := rcvCheckOrder{
   581  			ResourceConfigVersionID: rv.ID,
   582  			CheckOrder:              checkOrder,
   583  		}
   584  
   585  		rvs = append(rvs, rv)
   586  		checkOrderRVs = append(checkOrderRVs, checkOrderRV)
   587  	}
   588  
   589  	if len(rvs) == 0 {
   590  		return nil, Pagination{}, true, nil
   591  	}
   592  
   593  	newestRCVCheckOrder := checkOrderRVs[0]
   594  	oldestRCVCheckOrder := checkOrderRVs[len(checkOrderRVs)-1]
   595  
   596  	var pagination Pagination
   597  
   598  	var olderRCVId int
   599  	err = tx.QueryRow(`
   600  		SELECT v.id
   601  		FROM resource_config_versions v, resources r
   602  		WHERE v.check_order < $2 AND r.id = $1 AND v.resource_config_scope_id = r.resource_config_scope_id
   603  		ORDER BY v.check_order DESC
   604  		LIMIT 1
   605  	`, r.id, oldestRCVCheckOrder.CheckOrder).Scan(&olderRCVId)
   606  	if err != nil && err != sql.ErrNoRows {
   607  		return nil, Pagination{}, false, err
   608  	} else if err == nil {
   609  		pagination.Older = &Page{
   610  			To:    &olderRCVId,
   611  			Limit: page.Limit,
   612  		}
   613  	}
   614  
   615  	var newerRCVId int
   616  	err = tx.QueryRow(`
   617  		SELECT v.id
   618  		FROM resource_config_versions v, resources r
   619  		WHERE v.check_order > $2 AND r.id = $1 AND v.resource_config_scope_id = r.resource_config_scope_id
   620  		ORDER BY v.check_order ASC
   621  		LIMIT 1
   622  	`, r.id, newestRCVCheckOrder.CheckOrder).Scan(&newerRCVId)
   623  	if err != nil && err != sql.ErrNoRows {
   624  		return nil, Pagination{}, false, err
   625  	} else if err == nil {
   626  		pagination.Newer = &Page{
   627  			From:  &newerRCVId,
   628  			Limit: page.Limit,
   629  		}
   630  	}
   631  
   632  	err = tx.Commit()
   633  	if err != nil {
   634  		return nil, Pagination{}, false, nil
   635  	}
   636  
   637  	return rvs, pagination, true, nil
   638  }
   639  
   640  func (r *resource) EnableVersion(rcvID int) error {
   641  	return r.toggleVersion(rcvID, true)
   642  }
   643  
   644  func (r *resource) DisableVersion(rcvID int) error {
   645  	return r.toggleVersion(rcvID, false)
   646  }
   647  
   648  func (r *resource) PinVersion(rcvID int) (bool, error) {
   649  	tx, err := r.conn.Begin()
   650  	if err != nil {
   651  		return false, err
   652  	}
   653  	defer Rollback(tx)
   654  	var pinnedThroughConfig bool
   655  	err = tx.QueryRow(`
   656  		SELECT EXISTS (
   657  			SELECT 1
   658  			FROM resource_pins
   659  			WHERE resource_id = $1
   660  			AND config
   661  		)`, r.id).Scan(&pinnedThroughConfig)
   662  	if err != nil {
   663  		return false, err
   664  	}
   665  
   666  	if pinnedThroughConfig {
   667  		return false, ErrPinnedThroughConfig
   668  	}
   669  
   670  	results, err := tx.Exec(`
   671  	    INSERT INTO resource_pins(resource_id, version, comment_text, config)
   672  			VALUES ($1,
   673  				( SELECT rcv.version
   674  				FROM resource_config_versions rcv
   675  				WHERE rcv.id = $2 ),
   676  				'', false)
   677  			ON CONFLICT (resource_id) DO UPDATE SET version=EXCLUDED.version`, r.id, rcvID)
   678  	if err != nil {
   679  		if err == sql.ErrNoRows {
   680  			return false, nil
   681  		}
   682  		return false, err
   683  	}
   684  
   685  	rowsAffected, err := results.RowsAffected()
   686  	if err != nil {
   687  		return false, err
   688  	}
   689  
   690  	if rowsAffected != 1 {
   691  		return false, nil
   692  	}
   693  
   694  	err = requestScheduleForJobsUsingResource(tx, r.id)
   695  	if err != nil {
   696  		return false, err
   697  	}
   698  
   699  	err = tx.Commit()
   700  	if err != nil {
   701  		return false, err
   702  	}
   703  
   704  	return true, nil
   705  }
   706  
   707  func (r *resource) UnpinVersion() error {
   708  	tx, err := r.conn.Begin()
   709  	if err != nil {
   710  		return err
   711  	}
   712  
   713  	defer tx.Rollback()
   714  
   715  	results, err := psql.Delete("resource_pins").
   716  		Where(sq.Eq{"resource_pins.resource_id": r.id}).
   717  		RunWith(tx).
   718  		Exec()
   719  	if err != nil {
   720  		return err
   721  	}
   722  
   723  	rowsAffected, err := results.RowsAffected()
   724  	if err != nil {
   725  		return err
   726  	}
   727  
   728  	if rowsAffected != 1 {
   729  		return NonOneRowAffectedError{rowsAffected}
   730  	}
   731  
   732  	err = requestScheduleForJobsUsingResource(tx, r.id)
   733  	if err != nil {
   734  		return err
   735  	}
   736  
   737  	err = tx.Commit()
   738  	if err != nil {
   739  		return err
   740  	}
   741  
   742  	return nil
   743  }
   744  
   745  func (r *resource) toggleVersion(rcvID int, enable bool) error {
   746  	tx, err := r.conn.Begin()
   747  	if err != nil {
   748  		return err
   749  	}
   750  
   751  	defer Rollback(tx)
   752  
   753  	var results sql.Result
   754  	if enable {
   755  		results, err = tx.Exec(`
   756  			DELETE FROM resource_disabled_versions
   757  			WHERE resource_id = $1
   758  			AND version_md5 = (SELECT version_md5 FROM resource_config_versions rcv WHERE rcv.id = $2)
   759  			`, r.id, rcvID)
   760  	} else {
   761  		results, err = tx.Exec(`
   762  			INSERT INTO resource_disabled_versions (resource_id, version_md5)
   763  			SELECT $1, rcv.version_md5
   764  			FROM resource_config_versions rcv
   765  			WHERE rcv.id = $2
   766  			`, r.id, rcvID)
   767  	}
   768  	if err != nil {
   769  		return err
   770  	}
   771  
   772  	rowsAffected, err := results.RowsAffected()
   773  	if err != nil {
   774  		return err
   775  	}
   776  
   777  	if rowsAffected != 1 {
   778  		return NonOneRowAffectedError{rowsAffected}
   779  	}
   780  
   781  	err = requestScheduleForJobsUsingResource(tx, r.id)
   782  	if err != nil {
   783  		return err
   784  	}
   785  
   786  	return tx.Commit()
   787  }
   788  
   789  func (r *resource) NotifyScan() error {
   790  	return r.conn.Bus().Notify(fmt.Sprintf("resource_scan_%d", r.id))
   791  }
   792  
   793  func scanResource(r *resource, row scannable) error {
   794  	var (
   795  		configBlob                                        sql.NullString
   796  		nonce, rcID, rcScopeID, pinnedVersion, pinComment sql.NullString
   797  		lastCheckStartTime, lastCheckEndTime              pq.NullTime
   798  		pinnedThroughConfig                               sql.NullBool
   799  		pipelineInstanceVars                              sql.NullString
   800  	)
   801  
   802  	var build struct {
   803  		id        sql.NullInt64
   804  		name      sql.NullString
   805  		status    sql.NullString
   806  		startTime pq.NullTime
   807  		endTime   pq.NullTime
   808  	}
   809  
   810  	err := row.Scan(&r.id, &r.name, &r.type_, &configBlob, &lastCheckStartTime, &lastCheckEndTime, &r.pipelineID, &nonce, &rcID, &rcScopeID, &r.pipelineName, &pipelineInstanceVars, &r.teamID, &r.teamName, &pinnedVersion, &pinComment, &pinnedThroughConfig, &build.id, &build.name, &build.status, &build.startTime, &build.endTime)
   811  	if err != nil {
   812  		return err
   813  	}
   814  
   815  	r.lastCheckStartTime = lastCheckStartTime.Time
   816  	r.lastCheckEndTime = lastCheckEndTime.Time
   817  
   818  	es := r.conn.EncryptionStrategy()
   819  
   820  	var noncense *string
   821  	if nonce.Valid {
   822  		noncense = &nonce.String
   823  	}
   824  
   825  	if configBlob.Valid {
   826  		decryptedConfig, err := es.Decrypt(configBlob.String, noncense)
   827  		if err != nil {
   828  			return err
   829  		}
   830  
   831  		err = json.Unmarshal(decryptedConfig, &r.config)
   832  		if err != nil {
   833  			return err
   834  		}
   835  	} else {
   836  		r.config = atc.ResourceConfig{}
   837  	}
   838  
   839  	if pinnedVersion.Valid {
   840  		var version atc.Version
   841  		err = json.Unmarshal([]byte(pinnedVersion.String), &version)
   842  		if err != nil {
   843  			return err
   844  		}
   845  
   846  		if pinnedThroughConfig.Valid && pinnedThroughConfig.Bool {
   847  			r.configPinnedVersion = version
   848  			r.apiPinnedVersion = nil
   849  		} else {
   850  			r.configPinnedVersion = nil
   851  			r.apiPinnedVersion = version
   852  		}
   853  	} else {
   854  		r.apiPinnedVersion = nil
   855  		r.configPinnedVersion = nil
   856  	}
   857  
   858  	if pinComment.Valid {
   859  		r.pinComment = pinComment.String
   860  	} else {
   861  		r.pinComment = ""
   862  	}
   863  
   864  	if rcID.Valid {
   865  		r.resourceConfigID, err = strconv.Atoi(rcID.String)
   866  		if err != nil {
   867  			return err
   868  		}
   869  	}
   870  
   871  	if rcScopeID.Valid {
   872  		r.resourceConfigScopeID, err = strconv.Atoi(rcScopeID.String)
   873  		if err != nil {
   874  			return err
   875  		}
   876  	}
   877  
   878  	if pipelineInstanceVars.Valid {
   879  		err = json.Unmarshal([]byte(pipelineInstanceVars.String), &r.pipelineInstanceVars)
   880  		if err != nil {
   881  			return err
   882  		}
   883  	}
   884  
   885  	if build.id.Valid {
   886  		r.buildSummary = &atc.BuildSummary{
   887  			ID:   int(build.id.Int64),
   888  			Name: build.name.String,
   889  
   890  			Status: atc.BuildStatus(build.status.String),
   891  
   892  			TeamName: r.teamName,
   893  
   894  			PipelineID:           r.pipelineID,
   895  			PipelineName:         r.pipelineName,
   896  			PipelineInstanceVars: r.pipelineInstanceVars,
   897  		}
   898  
   899  		if build.startTime.Valid {
   900  			r.buildSummary.StartTime = build.startTime.Time.Unix()
   901  		}
   902  
   903  		if build.endTime.Valid {
   904  			r.buildSummary.EndTime = build.endTime.Time.Unix()
   905  		}
   906  	}
   907  
   908  	return nil
   909  }
   910  
   911  // The SELECT query orders the jobs for updating to prevent deadlocking.
   912  // Updating multiple rows using a SELECT subquery does not preserve the same
   913  // order for the updates, which can lead to deadlocking.
   914  func requestScheduleForJobsUsingResource(tx Tx, resourceID int) error {
   915  	rows, err := psql.Select("DISTINCT job_id").
   916  		From("job_inputs").
   917  		Where(sq.Eq{
   918  			"resource_id": resourceID,
   919  		}).
   920  		OrderBy("job_id DESC").
   921  		RunWith(tx).
   922  		Query()
   923  	if err != nil {
   924  		return err
   925  	}
   926  
   927  	var jobs []int
   928  	for rows.Next() {
   929  		var jid int
   930  		err = rows.Scan(&jid)
   931  		if err != nil {
   932  			return err
   933  		}
   934  
   935  		jobs = append(jobs, jid)
   936  	}
   937  
   938  	for _, j := range jobs {
   939  		_, err := psql.Update("jobs").
   940  			Set("schedule_requested", sq.Expr("now()")).
   941  			Where(sq.Eq{
   942  				"id": j,
   943  			}).
   944  			RunWith(tx).
   945  			Exec()
   946  		if err != nil {
   947  			return err
   948  		}
   949  	}
   950  
   951  	return nil
   952  }