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 }