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 }