forked from concourse/concourse
/
compute.go
115 lines (96 loc) · 3.26 KB
/
compute.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package algorithm
import (
"context"
"fmt"
"github.com/pf-qiu/concourse/v6/atc/db"
"github.com/pf-qiu/concourse/v6/tracing"
)
type Resolver interface {
Resolve(context.Context) (map[string]*versionCandidate, db.ResolutionFailure, error)
InputConfigs() db.InputConfigs
}
func New(versionsDB db.VersionsDB) *Algorithm {
return &Algorithm{
versionsDB: versionsDB,
}
}
type Algorithm struct {
versionsDB db.VersionsDB
}
func (a *Algorithm) Compute(
ctx context.Context,
job db.Job,
inputs db.InputConfigs,
) (db.InputMapping, bool, bool, error) {
ctx, span := tracing.StartSpan(ctx, "Algorithm.Compute", tracing.Attrs{
"pipeline": job.PipelineName(),
"job": job.Name(),
})
defer span.End()
resolvers, err := constructResolvers(a.versionsDB, inputs)
if err != nil {
return nil, false, false, fmt.Errorf("construct resolvers: %w", err)
}
return a.computeResolvers(ctx, resolvers)
}
func (a *Algorithm) computeResolvers(
ctx context.Context,
resolvers []Resolver,
) (db.InputMapping, bool, bool, error) {
finalHasNext := false
finalResolved := true
finalMapping := db.InputMapping{}
for _, resolver := range resolvers {
versionCandidates, resolveErr, err := resolver.Resolve(ctx)
if err != nil {
return nil, false, false, fmt.Errorf("resolve: %w", err)
}
// determines if the algorithm successfully resolved all inputs depending
// on if all resolvers did not return a resolve error
finalResolved = finalResolved && (resolveErr == "")
// converts the version candidates into an object that is recognizable by
// other components. also computes the first occurrence for all satisfiable
// inputs
finalMapping, err = a.candidatesToInputMapping(ctx, finalMapping, resolver.InputConfigs(), versionCandidates, resolveErr)
if err != nil {
return nil, false, false, fmt.Errorf("candidates to input mapping: %w", err)
}
// if any one of the resolvers has a version candidate that has an unused
// next every version, the algorithm should return true for being able to
// be run again
finalHasNext = finalHasNext || a.finalizeHasNext(versionCandidates)
}
return finalMapping, finalResolved, finalHasNext, nil
}
func (a *Algorithm) finalizeHasNext(versionCandidates map[string]*versionCandidate) bool {
hasNextCombined := false
for _, candidate := range versionCandidates {
hasNextCombined = hasNextCombined || candidate.HasNextEveryVersion
}
return hasNextCombined
}
func (a *Algorithm) candidatesToInputMapping(ctx context.Context, mapping db.InputMapping, inputConfigs db.InputConfigs, candidates map[string]*versionCandidate, resolveErr db.ResolutionFailure) (db.InputMapping, error) {
for _, input := range inputConfigs {
if resolveErr != "" {
mapping[input.Name] = db.InputResult{
ResolveError: resolveErr,
}
} else {
firstOcc, err := a.versionsDB.IsFirstOccurrence(ctx, input.JobID, input.Name, candidates[input.Name].Version, input.ResourceID)
if err != nil {
return nil, err
}
mapping[input.Name] = db.InputResult{
Input: &db.AlgorithmInput{
AlgorithmVersion: db.AlgorithmVersion{
ResourceID: input.ResourceID,
Version: candidates[input.Name].Version,
},
FirstOccurrence: firstOcc,
},
PassedBuildIDs: candidates[input.Name].SourceBuildIds,
}
}
}
return mapping, nil
}