forked from vmware-archive/atc
/
config_source.go
240 lines (197 loc) · 6.72 KB
/
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
package exec
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
"strconv"
"strings"
"github.com/concourse/atc"
"github.com/concourse/baggageclaim"
)
//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 SourceRepository.
FetchConfig(*SourceRepository) (atc.TaskConfig, error)
Warnings() []string
}
// StaticConfigSource represents a statically configured TaskConfig.
type StaticConfigSource struct {
Plan atc.TaskPlan
}
// FetchConfig returns the configuration.
func (configSource StaticConfigSource) FetchConfig(*SourceRepository) (atc.TaskConfig, error) {
taskConfig := atc.TaskConfig{}
if configSource.Plan.Config != nil {
taskConfig = *configSource.Plan.Config
}
if configSource.Plan.Params == nil {
return taskConfig, nil
}
if taskConfig.Params == nil {
taskConfig.Params = map[string]string{}
}
for key, val := range configSource.Plan.Params {
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 StaticConfigSource) Warnings() []string {
warnings := []string{}
if configSource.Plan.ConfigPath != "" && configSource.Plan.Config != nil {
warnings = append(warnings, "\x1b[31mDEPRECATION WARNING: Specifying both `file` and `config.params` in a task step is deprecated, use params on task step directly\x1b[0m")
}
return warnings
}
// DeprecationConfigSource represents a statically configured TaskConfig.
type DeprecationConfigSource struct {
Delegate TaskConfigSource
Stderr io.Writer
}
// FetchConfig returns the configuration. It cannot fail.
func (configSource DeprecationConfigSource) FetchConfig(repo *SourceRepository) (atc.TaskConfig, error) {
taskConfig, err := configSource.Delegate.FetchConfig(repo)
if err != nil {
return atc.TaskConfig{}, err
}
for _, warning := range configSource.Delegate.Warnings() {
fmt.Fprintln(configSource.Stderr, warning)
}
return taskConfig, nil
}
func (configSource DeprecationConfigSource) Warnings() []string {
return []string{}
}
// FileConfigSource represents a dynamically configured TaskConfig, which will
// be fetched from a specified file in the SourceRepository.
type FileConfigSource struct {
Path string
}
// FetchConfig reads the specified file from the SourceRepository 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 ArtifactSource in the SourceRepository 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(repo *SourceRepository) (atc.TaskConfig, error) {
segs := strings.SplitN(configSource.Path, "/", 2)
if len(segs) != 2 {
return atc.TaskConfig{}, UnspecifiedArtifactSourceError{configSource.Path}
}
sourceName := SourceName(segs[0])
filePath := segs[1]
source, found := repo.SourceFor(sourceName)
if !found {
return atc.TaskConfig{}, UnknownArtifactSourceError{sourceName}
}
stream, err := source.StreamFile(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()
streamedFile, err := ioutil.ReadAll(stream)
if err != nil {
return atc.TaskConfig{}, err
}
config, err := atc.LoadTaskConfig(streamedFile)
if err != nil {
return atc.TaskConfig{}, fmt.Errorf("failed to load %s: %s", configSource.Path, err)
}
return config, nil
}
func (configSource FileConfigSource) Warnings() []string {
return []string{}
}
// MergedConfigSource is used to join two config sources together.
type MergedConfigSource struct {
A TaskConfigSource
B TaskConfigSource
}
// FetchConfig fetches both config sources, and merges the second config source
// into the first. This allows the user to set params required by a task loaded
// from a file by providing them in static configuration.
func (configSource MergedConfigSource) FetchConfig(source *SourceRepository) (atc.TaskConfig, error) {
aConfig, err := configSource.A.FetchConfig(source)
if err != nil {
return atc.TaskConfig{}, err
}
bConfig, err := configSource.B.FetchConfig(source)
if err != nil {
return atc.TaskConfig{}, err
}
return aConfig.Merge(bConfig), nil
}
func (configSource MergedConfigSource) Warnings() []string {
warnings := []string{}
warnings = append(warnings, configSource.A.Warnings()...)
warnings = append(warnings, configSource.B.Warnings()...)
return warnings
}
// 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(source *SourceRepository) (atc.TaskConfig, error) {
config, err := configSource.ConfigSource.FetchConfig(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 SourceName specified by the
// path does not exist in the SourceRepository.
type UnknownArtifactSourceError struct {
SourceName SourceName
}
// Error returns a human-friendly error message.
func (err UnknownArtifactSourceError) Error() string {
return fmt.Sprintf("unknown artifact source: %s", err.SourceName)
}
// 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)
}