github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/resource_cache.go (about) 1 package db 2 3 import ( 4 "crypto/sha256" 5 "database/sql" 6 "encoding/json" 7 "errors" 8 "fmt" 9 10 sq "github.com/Masterminds/squirrel" 11 "github.com/pf-qiu/concourse/v6/atc" 12 "github.com/pf-qiu/concourse/v6/atc/db/lock" 13 ) 14 15 var ErrResourceCacheAlreadyExists = errors.New("resource-cache-already-exists") 16 var ErrResourceCacheDisappeared = errors.New("resource-cache-disappeared") 17 18 // ResourceCache represents an instance of a ResourceConfig's version. 19 // 20 // A ResourceCache is created by a `get`, an `image_resource`, or a resource 21 // type in a pipeline. 22 // 23 // ResourceCaches are garbage-collected by gc.ResourceCacheCollector. 24 type ResourceCacheDescriptor struct { 25 ResourceConfigDescriptor ResourceConfigDescriptor // The resource configuration. 26 Version atc.Version // The version of the resource. 27 Params atc.Params // The params used when fetching the version. 28 } 29 30 func (cache *ResourceCacheDescriptor) find(tx Tx, lockFactory lock.LockFactory, conn Conn) (UsedResourceCache, bool, error) { 31 resourceConfig, found, err := cache.ResourceConfigDescriptor.find(tx, lockFactory, conn) 32 if err != nil { 33 return nil, false, err 34 } 35 36 if !found { 37 return nil, false, nil 38 } 39 40 return cache.findWithResourceConfig(tx, resourceConfig, lockFactory, conn) 41 } 42 43 func (cache *ResourceCacheDescriptor) findOrCreate( 44 tx Tx, 45 lockFactory lock.LockFactory, 46 conn Conn, 47 ) (UsedResourceCache, error) { 48 resourceConfig, err := cache.ResourceConfigDescriptor.findOrCreate(tx, lockFactory, conn) 49 if err != nil { 50 return nil, err 51 } 52 53 rc, found, err := cache.findWithResourceConfig(tx, resourceConfig, lockFactory, conn) 54 if err != nil { 55 return nil, err 56 } 57 58 if !found { 59 var id int 60 err = psql.Insert("resource_caches"). 61 Columns( 62 "resource_config_id", 63 "version", 64 "version_md5", 65 "params_hash", 66 ). 67 Values( 68 resourceConfig.ID(), 69 cache.version(), 70 sq.Expr("md5(?)", cache.version()), 71 paramsHash(cache.Params), 72 ). 73 Suffix(` 74 ON CONFLICT (resource_config_id, version_md5, params_hash) DO UPDATE SET 75 resource_config_id = EXCLUDED.resource_config_id, 76 version = EXCLUDED.version, 77 version_md5 = EXCLUDED.version_md5, 78 params_hash = EXCLUDED.params_hash 79 RETURNING id 80 `). 81 RunWith(tx). 82 QueryRow(). 83 Scan(&id) 84 if err != nil { 85 return nil, err 86 } 87 88 rc = &usedResourceCache{ 89 id: id, 90 version: cache.Version, 91 resourceConfig: resourceConfig, 92 lockFactory: lockFactory, 93 conn: conn, 94 } 95 } 96 97 return rc, nil 98 } 99 100 func (cache *ResourceCacheDescriptor) use( 101 tx Tx, 102 rc UsedResourceCache, 103 user ResourceCacheUser, 104 ) error { 105 cols := user.SQLMap() 106 cols["resource_cache_id"] = rc.ID() 107 108 var resourceCacheUseExists int 109 err := psql.Select("1"). 110 From("resource_cache_uses"). 111 Where(sq.Eq(cols)). 112 RunWith(tx). 113 QueryRow(). 114 Scan(&resourceCacheUseExists) 115 if err != nil { 116 if err != sql.ErrNoRows { 117 return err 118 } 119 } 120 121 if err == nil { 122 // use already exists 123 return nil 124 } 125 126 _, err = psql.Insert("resource_cache_uses"). 127 SetMap(cols). 128 RunWith(tx). 129 Exec() 130 return err 131 } 132 133 func (cache *ResourceCacheDescriptor) findWithResourceConfig(tx Tx, resourceConfig ResourceConfig, lockFactory lock.LockFactory, conn Conn) (UsedResourceCache, bool, error) { 134 var id int 135 err := psql.Select("id"). 136 From("resource_caches"). 137 Where(sq.Eq{ 138 "resource_config_id": resourceConfig.ID(), 139 "params_hash": paramsHash(cache.Params), 140 }). 141 Where(sq.Expr("version_md5 = md5(?)", cache.version())). 142 Suffix("FOR SHARE"). 143 RunWith(tx). 144 QueryRow(). 145 Scan(&id) 146 if err != nil { 147 if err == sql.ErrNoRows { 148 return nil, false, nil 149 } 150 151 return nil, false, err 152 } 153 154 return &usedResourceCache{ 155 id: id, 156 version: cache.Version, 157 resourceConfig: resourceConfig, 158 lockFactory: lockFactory, 159 conn: conn, 160 }, true, nil 161 } 162 163 func (cache *ResourceCacheDescriptor) version() string { 164 j, _ := json.Marshal(cache.Version) 165 return string(j) 166 } 167 168 func paramsHash(p atc.Params) string { 169 if p != nil { 170 return mapHash(p) 171 } 172 173 return mapHash(atc.Params{}) 174 } 175 176 // UsedResourceCache is created whenever a ResourceCache is Created and/or 177 // Used. 178 // 179 // So long as the UsedResourceCache exists, the underlying ResourceCache can 180 // not be removed. 181 // 182 // UsedResourceCaches become unused by the gc.ResourceCacheCollector, which may 183 // then lead to the ResourceCache being garbage-collected. 184 // 185 // See FindOrCreateForBuild, FindOrCreateForResource, and 186 // FindOrCreateForResourceType for more information on when it becomes unused. 187 188 //go:generate counterfeiter . UsedResourceCache 189 190 type UsedResourceCache interface { 191 ID() int 192 Version() atc.Version 193 194 ResourceConfig() ResourceConfig 195 196 Destroy(Tx) (bool, error) 197 BaseResourceType() *UsedBaseResourceType 198 } 199 200 type usedResourceCache struct { 201 id int 202 resourceConfig ResourceConfig 203 version atc.Version 204 205 lockFactory lock.LockFactory 206 conn Conn 207 } 208 209 func (cache *usedResourceCache) ID() int { return cache.id } 210 func (cache *usedResourceCache) ResourceConfig() ResourceConfig { return cache.resourceConfig } 211 func (cache *usedResourceCache) Version() atc.Version { return cache.version } 212 213 func (cache *usedResourceCache) Destroy(tx Tx) (bool, error) { 214 rows, err := psql.Delete("resource_caches"). 215 Where(sq.Eq{ 216 "id": cache.id, 217 }). 218 RunWith(tx). 219 Exec() 220 if err != nil { 221 return false, err 222 } 223 224 affected, err := rows.RowsAffected() 225 if err != nil { 226 return false, err 227 } 228 229 if affected == 0 { 230 return false, ErrResourceCacheDisappeared 231 } 232 233 return true, nil 234 } 235 236 func (cache *usedResourceCache) BaseResourceType() *UsedBaseResourceType { 237 if cache.resourceConfig.CreatedByBaseResourceType() != nil { 238 return cache.resourceConfig.CreatedByBaseResourceType() 239 } 240 241 return cache.resourceConfig.CreatedByResourceCache().BaseResourceType() 242 } 243 244 func mapHash(m map[string]interface{}) string { 245 j, _ := json.Marshal(m) 246 return fmt.Sprintf("%x", sha256.Sum256(j)) 247 }