1package db
2
3import (
4	"database/sql"
5	"encoding/json"
6
7	sq "github.com/Masterminds/squirrel"
8	"github.com/concourse/concourse/atc"
9	"github.com/concourse/concourse/atc/db/lock"
10)
11
12//go:generate counterfeiter . ResourceCacheFactory
13
14type ResourceCacheFactory interface {
15	FindOrCreateResourceCache(
16		resourceCacheUser ResourceCacheUser,
17		resourceTypeName string,
18		version atc.Version,
19		source atc.Source,
20		params atc.Params,
21		resourceTypes atc.VersionedResourceTypes,
22	) (UsedResourceCache, error)
23
24	// changing resource cache to interface to allow updates on object is not feasible.
25	// Since we need to pass it recursively in ResourceConfig.
26	// Also, metadata will be available to us before we create resource cache so this
27	// method can be removed at that point. See  https://github.com/concourse/concourse/issues/534
28	UpdateResourceCacheMetadata(UsedResourceCache, []atc.MetadataField) error
29	ResourceCacheMetadata(UsedResourceCache) (ResourceConfigMetadataFields, error)
30
31	FindResourceCacheByID(id int) (UsedResourceCache, bool, error)
32}
33
34type resourceCacheFactory struct {
35	conn        Conn
36	lockFactory lock.LockFactory
37}
38
39func NewResourceCacheFactory(conn Conn, lockFactory lock.LockFactory) ResourceCacheFactory {
40	return &resourceCacheFactory{
41		conn:        conn,
42		lockFactory: lockFactory,
43	}
44}
45
46func (f *resourceCacheFactory) FindOrCreateResourceCache(
47	resourceCacheUser ResourceCacheUser,
48	resourceTypeName string,
49	version atc.Version,
50	source atc.Source,
51	params atc.Params,
52	resourceTypes atc.VersionedResourceTypes,
53) (UsedResourceCache, error) {
54	resourceConfigDescriptor, err := constructResourceConfigDescriptor(resourceTypeName, source, resourceTypes)
55	if err != nil {
56		return nil, err
57	}
58
59	resourceCache := ResourceCacheDescriptor{
60		ResourceConfigDescriptor: resourceConfigDescriptor,
61		Version:                  version,
62		Params:                   params,
63	}
64
65	tx, err := f.conn.Begin()
66	if err != nil {
67		return nil, err
68	}
69
70	defer Rollback(tx)
71
72	usedResourceCache, err := resourceCache.findOrCreate(tx, f.lockFactory, f.conn)
73	if err != nil {
74		return nil, err
75	}
76
77	err = resourceCache.use(tx, usedResourceCache, resourceCacheUser)
78	if err != nil {
79		return nil, err
80	}
81
82	err = tx.Commit()
83	if err != nil {
84		return nil, err
85	}
86
87	return usedResourceCache, nil
88}
89
90func (f *resourceCacheFactory) UpdateResourceCacheMetadata(resourceCache UsedResourceCache, metadata []atc.MetadataField) error {
91	metadataJSON, err := json.Marshal(metadata)
92	if err != nil {
93		return err
94	}
95	_, err = psql.Update("resource_caches").
96		Set("metadata", metadataJSON).
97		Where(sq.Eq{"id": resourceCache.ID()}).
98		RunWith(f.conn).
99		Exec()
100	return err
101}
102
103func (f *resourceCacheFactory) ResourceCacheMetadata(resourceCache UsedResourceCache) (ResourceConfigMetadataFields, error) {
104	var metadataJSON sql.NullString
105	err := psql.Select("metadata").
106		From("resource_caches").
107		Where(sq.Eq{"id": resourceCache.ID()}).
108		RunWith(f.conn).
109		QueryRow().
110		Scan(&metadataJSON)
111	if err != nil {
112		return nil, err
113	}
114
115	var metadata []ResourceConfigMetadataField
116	if metadataJSON.Valid {
117		err = json.Unmarshal([]byte(metadataJSON.String), &metadata)
118		if err != nil {
119			return nil, err
120		}
121	}
122
123	return metadata, nil
124}
125
126func (f *resourceCacheFactory) FindResourceCacheByID(id int) (UsedResourceCache, bool, error) {
127	tx, err := f.conn.Begin()
128	if err != nil {
129		return nil, false, err
130	}
131
132	defer Rollback(tx)
133
134	return findResourceCacheByID(tx, id, f.lockFactory, f.conn)
135}
136
137func findResourceCacheByID(tx Tx, resourceCacheID int, lock lock.LockFactory, conn Conn) (UsedResourceCache, bool, error) {
138	var rcID int
139	var versionBytes string
140
141	err := psql.Select("resource_config_id", "version").
142		From("resource_caches").
143		Where(sq.Eq{"id": resourceCacheID}).
144		RunWith(tx).
145		QueryRow().
146		Scan(&rcID, &versionBytes)
147
148	if err != nil {
149		if err == sql.ErrNoRows {
150			return nil, false, nil
151		}
152		return nil, false, err
153	}
154
155	var version atc.Version
156	err = json.Unmarshal([]byte(versionBytes), &version)
157	if err != nil {
158		return nil, false, err
159	}
160
161	rc, found, err := findResourceConfigByID(tx, rcID, lock, conn)
162	if err != nil {
163		return nil, false, err
164	}
165
166	if !found {
167		return nil, false, nil
168	}
169
170	usedResourceCache := &usedResourceCache{
171		id:             resourceCacheID,
172		version:        version,
173		resourceConfig: rc,
174		lockFactory:    lock,
175		conn:           conn,
176	}
177
178	return usedResourceCache, true, nil
179}
180