forked from concourse/concourse
/
task_config_source.go
243 lines (204 loc) · 7.6 KB
/
task_config_source.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package exec
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"strconv"
"strings"
"code.cloudfoundry.org/lager"
"github.com/concourse/baggageclaim"
"github.com/chenbh/concourse/v6/atc"
"github.com/chenbh/concourse/v6/atc/exec/build"
"github.com/chenbh/concourse/v6/atc/worker"
"github.com/chenbh/concourse/v6/vars"
"sigs.k8s.io/yaml"
)
//go:generate counterfeiter . TaskConfigSource
// TaskConfigSource is used to determine a Task step's TaskConfig.
type TaskConfigSource interface {
// FetchConfig returns the TaskConfig, and may have to a task config file out
// of the artifact.Repository.
FetchConfig(context.Context, lager.Logger, *build.Repository) (atc.TaskConfig, error)
Warnings() []string
}
// StaticConfigSource represents a statically configured TaskConfig.
type StaticConfigSource struct {
Config *atc.TaskConfig
}
// FetchConfig returns the configuration.
func (configSource StaticConfigSource) FetchConfig(context.Context, lager.Logger, *build.Repository) (atc.TaskConfig, error) {
taskConfig := atc.TaskConfig{}
if configSource.Config != nil {
taskConfig = *configSource.Config
}
return taskConfig, nil
}
func (configSource StaticConfigSource) Warnings() []string {
return []string{}
}
// FileConfigSource represents a dynamically configured TaskConfig, which will
// be fetched from a specified file in the artifact.Repository.
type FileConfigSource struct {
ConfigPath string
Client worker.Client
}
// FetchConfig reads the specified file from the artifact.Repository and loads the
// TaskConfig contained therein (expecting it to be YAML format).
//
// The path must be in the format SOURCE_NAME/FILE/PATH.yml. The SOURCE_NAME
// will be used to determine the StreamableArtifactSource in the artifact.Repository to
// stream the file out of.
//
// If the source name is missing (i.e. if the path is just "foo.yml"),
// UnspecifiedArtifactSourceError is returned.
//
// If the specified source name cannot be found, UnknownArtifactSourceError is
// returned.
//
// If the task config file is not found, or is invalid YAML, or is an invalid
// task configuration, the respective errors will be bubbled up.
func (configSource FileConfigSource) FetchConfig(ctx context.Context, logger lager.Logger, repo *build.Repository) (atc.TaskConfig, error) {
segs := strings.SplitN(configSource.ConfigPath, "/", 2)
if len(segs) != 2 {
return atc.TaskConfig{}, UnspecifiedArtifactSourceError{configSource.ConfigPath}
}
sourceName := build.ArtifactName(segs[0])
filePath := segs[1]
artifact, found := repo.ArtifactFor(sourceName)
if !found {
return atc.TaskConfig{}, UnknownArtifactSourceError{sourceName, configSource.ConfigPath}
}
stream, err := configSource.Client.StreamFileFromArtifact(ctx, logger, artifact, filePath)
if err != nil {
if err == baggageclaim.ErrFileNotFound {
return atc.TaskConfig{}, fmt.Errorf("task config '%s/%s' not found", sourceName, filePath)
}
return atc.TaskConfig{}, err
}
defer stream.Close()
byteConfig, err := ioutil.ReadAll(stream)
if err != nil {
return atc.TaskConfig{}, err
}
config, err := atc.NewTaskConfig(byteConfig)
if err != nil {
return atc.TaskConfig{}, fmt.Errorf("failed to create task config from bytes %s: %s", configSource.ConfigPath, err)
}
return config, nil
}
func (configSource FileConfigSource) Warnings() []string {
return []string{}
}
// OverrideParamsConfigSource is used to override params in a config source
type OverrideParamsConfigSource struct {
ConfigSource TaskConfigSource
Params atc.Params
WarningList []string
}
// FetchConfig overrides parameters, allowing the user to set params required by a task loaded
// from a file by providing them in static configuration.
func (configSource *OverrideParamsConfigSource) FetchConfig(ctx context.Context, logger lager.Logger, source *build.Repository) (atc.TaskConfig, error) {
taskConfig, err := configSource.ConfigSource.FetchConfig(ctx, logger, source)
if err != nil {
return atc.TaskConfig{}, err
}
if taskConfig.Params == nil {
taskConfig.Params = atc.TaskEnv{}
}
for key, val := range configSource.Params {
if _, exists := taskConfig.Params[key]; !exists {
configSource.WarningList = append(configSource.WarningList, fmt.Sprintf("%s was defined in pipeline but missing from task file", key))
}
switch v := val.(type) {
case string:
taskConfig.Params[key] = v
case float64:
if math.Floor(v) == v {
taskConfig.Params[key] = strconv.FormatInt(int64(v), 10)
} else {
taskConfig.Params[key] = strconv.FormatFloat(v, 'f', -1, 64)
}
default:
bs, err := json.Marshal(val)
if err != nil {
return atc.TaskConfig{}, err
}
taskConfig.Params[key] = string(bs)
}
}
return taskConfig, nil
}
func (configSource OverrideParamsConfigSource) Warnings() []string {
return configSource.WarningList
}
// InterpolateTemplateConfigSource represents a config source interpolated by template vars
type InterpolateTemplateConfigSource struct {
ConfigSource TaskConfigSource
Vars []vars.Variables
ExpectAllKeys bool
}
// FetchConfig returns the interpolated configuration
func (configSource InterpolateTemplateConfigSource) FetchConfig(ctx context.Context, logger lager.Logger, source *build.Repository) (atc.TaskConfig, error) {
taskConfig, err := configSource.ConfigSource.FetchConfig(ctx, logger, source)
if err != nil {
return atc.TaskConfig{}, err
}
byteConfig, err := yaml.Marshal(taskConfig)
if err != nil {
return atc.TaskConfig{}, fmt.Errorf("failed to marshal task config: %s", err)
}
// process task config using the provided variables
byteConfig, err = vars.NewTemplateResolver(byteConfig, configSource.Vars).Resolve(configSource.ExpectAllKeys, true)
if err != nil {
return atc.TaskConfig{}, fmt.Errorf("failed to interpolate task config: %s", err)
}
taskConfig, err = atc.NewTaskConfig(byteConfig)
if err != nil {
return atc.TaskConfig{}, fmt.Errorf("failed to create task config from bytes: %s", err)
}
return taskConfig, nil
}
func (configSource InterpolateTemplateConfigSource) Warnings() []string {
return []string{}
}
// ValidatingConfigSource delegates to another ConfigSource, and validates its
// task config.
type ValidatingConfigSource struct {
ConfigSource TaskConfigSource
}
// FetchConfig fetches the config using the underlying ConfigSource, and checks
// that it's valid.
func (configSource ValidatingConfigSource) FetchConfig(ctx context.Context, logger lager.Logger, source *build.Repository) (atc.TaskConfig, error) {
config, err := configSource.ConfigSource.FetchConfig(ctx, logger, source)
if err != nil {
return atc.TaskConfig{}, err
}
if err := config.Validate(); err != nil {
return atc.TaskConfig{}, err
}
return config, nil
}
func (configSource ValidatingConfigSource) Warnings() []string {
return configSource.ConfigSource.Warnings()
}
// UnknownArtifactSourceError is returned when the artifact.ArtifactName specified by the
// path does not exist in the artifact.Repository.
type UnknownArtifactSourceError struct {
SourceName build.ArtifactName
ConfigPath string
}
// Error returns a human-friendly error message.
func (err UnknownArtifactSourceError) Error() string {
return fmt.Sprintf("unknown artifact source: '%s' in task config file path '%s'", err.SourceName, err.ConfigPath)
}
// UnspecifiedArtifactSourceError is returned when the specified path is of a
// file in the toplevel directory, and so it does not indicate a SourceName.
type UnspecifiedArtifactSourceError struct {
Path string
}
// Error returns a human-friendly error message.
func (err UnspecifiedArtifactSourceError) Error() string {
return fmt.Sprintf("config path '%s' does not specify where the file lives", err.Path)
}