-
Notifications
You must be signed in to change notification settings - Fork 785
Expand file tree
/
Copy pathWaveConfig.groovy
More file actions
317 lines (256 loc) · 11.2 KB
/
WaveConfig.groovy
File metadata and controls
317 lines (256 loc) · 11.2 KB
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*
* Copyright 2013-2026, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seqera.wave.plugin.config
import groovy.transform.CompileStatic
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import io.seqera.wave.api.BuildCompression
import io.seqera.wave.api.ScanLevel
import io.seqera.wave.api.ScanMode
import io.seqera.wave.config.CondaOpts
import nextflow.config.spec.ConfigOption
import nextflow.config.spec.ConfigScope
import nextflow.config.spec.ScopeName
import nextflow.script.dsl.Description
import nextflow.file.FileHelper
import nextflow.util.Duration
/**
* Model Wave client configuration
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@ScopeName("wave")
@Description("""
The `wave` scope provides advanced configuration for the use of [Wave containers](https://docs.seqera.io/wave).
""")
@Slf4j
@ToString(includeNames = true, includePackage = false, includeFields = true, useGetters = false)
@CompileStatic
class WaveConfig implements ConfigScope {
final private static String DEF_ENDPOINT = 'https://wave.seqera.io'
final private static List<String> DEF_STRATEGIES = List.of('container','dockerfile','conda')
final BuildOpts build
@ConfigOption
@Description("""
Enable the use of Wave containers (default: `false`).
""")
final boolean enabled
@ConfigOption
@Description("""
The Wave service endpoint (default: `https://wave.seqera.io`).
""")
final String endpoint
@ConfigOption
@Description("""
Enable Wave container freezing (default: `false`). Wave will provision a non-ephemeral container image that will be pushed to a container repository of your choice.
See also: `wave.build.repository` and `wave.build.cacheRepository`
""")
final boolean freeze
final HttpOpts httpClient
@ConfigOption
@Description("""
Enable Wave container mirroring (default: `false`). Wave will mirror (i.e. copy) the containers in your pipeline to a container registry of your choice, so that pipeline tasks can pull the containers from this registry instead of the original one.
See also: `wave.build.repository`
""")
final boolean mirror
final RetryOpts retryPolicy
final ScanOpts scan
@ConfigOption(types=[String])
@Description("""
The strategy to be used when resolving multiple Wave container requirements (default: `'container,dockerfile,conda'`).
""")
final List<String> strategy
final private Boolean bundleProjectResources
final private List<URL> containerConfigUrl
final private Boolean preserveFileTimestamp
final private Duration tokensCacheMaxDuration
/* required by extension point -- do not remove */
WaveConfig() {}
WaveConfig(Map opts, Map<String,String> env=System.getenv()) {
this.build = new BuildOpts(opts.build as Map ?: Collections.emptyMap())
this.enabled = opts.enabled as boolean
this.endpoint = (opts.endpoint?.toString() ?: env.get('WAVE_API_ENDPOINT') ?: DEF_ENDPOINT)?.stripEnd('/')
this.freeze = opts.freeze as boolean
this.httpClient = new HttpOpts(opts.httpClient as Map ?: Collections.emptyMap())
this.mirror = opts.mirror as boolean
this.retryPolicy = retryOpts0(opts)
this.scan = new ScanOpts(opts.scan as Map ?: Collections.emptyMap())
this.strategy = parseStrategy(opts.strategy)
this.bundleProjectResources = opts.bundleProjectResources
this.containerConfigUrl = parseConfig(opts, env)
this.preserveFileTimestamp = opts.preserveFileTimestamp as Boolean
this.tokensCacheMaxDuration = opts.navigate('tokens.cache.maxDuration', '30m') as Duration
validateConfig()
}
Boolean enabled() { this.enabled }
String endpoint() { this.endpoint }
CondaOpts condaOpts() { this.build.conda }
RetryOpts retryOpts() { this.retryPolicy }
HttpOpts httpOpts() { this.httpClient }
List<String> strategy() { this.strategy }
boolean freezeMode() { this.freeze }
boolean mirrorMode() { this.mirror }
boolean preserveFileTimestamp() { return this.preserveFileTimestamp }
boolean bundleProjectResources() { bundleProjectResources }
String buildRepository() { build.repository }
String cacheRepository() { build.cacheRepository }
String buildTemplate() { build.template }
Duration buildMaxDuration() { build.maxDuration }
BuildCompression buildCompression() { build.compression }
private void validateConfig() {
def scheme= FileHelper.getUrlProtocol(endpoint)
if( scheme !in ['http','https'] )
throw new IllegalArgumentException("Endpoint URL should start with 'http:' or 'https:' protocol prefix - offending value: '$endpoint'")
if( FileHelper.getUrlProtocol(build.repository) )
throw new IllegalArgumentException("Config setting 'wave.build.repository' should not include any protocol prefix - offending value: '$build.repository'")
if( FileHelper.getUrlProtocol(build.cacheRepository) )
throw new IllegalArgumentException("Config setting 'wave.build.cacheRepository' should not include any protocol prefix - offending value: '$build.cacheRepository'")
}
private RetryOpts retryOpts0(Map opts) {
if( opts.retryPolicy )
return new RetryOpts(opts.retryPolicy as Map)
if( opts.retry ) {
log.warn "Configuration options 'wave.retry' has been deprecated - replace it with 'wave.retryPolicy'"
return new RetryOpts(opts.retry as Map)
}
return new RetryOpts(Collections.emptyMap())
}
protected List<String> parseStrategy(value) {
if( !value ) {
log.debug "Wave strategy not specified - using default: $DEF_STRATEGIES"
return DEF_STRATEGIES
}
List<String> result
if( value instanceof CharSequence )
result = value.tokenize(',') .collect(it -> it.toString().trim())
else if( value instanceof List )
result = value.collect(it -> it.toString().trim())
else
throw new IllegalArgumentException("Invalid value for 'wave.strategy' configuration attribute - offending value: $value")
for( String it : result ) {
if( it !in DEF_STRATEGIES)
throw new IllegalArgumentException("Invalid value for 'wave.strategy' configuration attribute - offending value: $it")
}
return result
}
protected List<URL> parseConfig(Map opts, Map<String,String> env) {
List<String> result = new ArrayList<>(10)
if( !opts.containerConfigUrl && env.get('WAVE_CONTAINER_CONFIG_URL') ) {
result.add(checkUrl(env.get('WAVE_CONTAINER_CONFIG_URL')))
}
else if( opts.containerConfigUrl instanceof CharSequence ) {
result.add(checkUrl(opts.containerConfigUrl.toString()))
}
else if( opts.containerConfigUrl instanceof List ) {
for( def it : opts.containerConfigUrl ) {
result.add(checkUrl(it.toString()))
}
}
return result.collect(it -> new URL(it))
}
private String checkUrl(String value) {
if( value && (!value.startsWith('http://') && !value.startsWith('https://')))
throw new IllegalArgumentException("Wave container config URL should start with 'http:' or 'https:' protocol prefix - offending value: $value")
return value
}
List<URL> containerConfigUrl() {
return containerConfigUrl ?: Collections.<URL>emptyList()
}
Duration tokensCacheMaxDuration() {
return tokensCacheMaxDuration
}
ScanMode scanMode() {
return scan.mode
}
List<ScanLevel> scanAllowedLevels() {
return scan.allowedLevels
}
}
@ToString(includeNames = true, includePackage = false, includeFields = true)
@CompileStatic
class ScanOpts implements ConfigScope {
@ConfigOption(types=[String])
@Description("""
Comma-separated list of allowed vulnerability levels when scanning containers for security vulnerabilities in `required` mode.
""")
final List<ScanLevel> allowedLevels
@ConfigOption(types=[String])
@Description("""
Enable Wave container security scanning. Wave will scan the containers in your pipeline for security vulnerabilities.
""")
final ScanMode mode
ScanOpts(Map opts) {
allowedLevels = parseScanLevels(opts.allowedLevels)
mode = opts.mode as ScanMode
}
protected List<ScanLevel> parseScanLevels(value) {
if( !value )
return null
if( value instanceof CharSequence ) {
final str = value.toString()
value = str.tokenize(',').collect(it->it.trim())
}
if( value instanceof List ) {
return (value as List).collect(it-> ScanLevel.valueOf(it.toString().toUpperCase()))
}
throw new IllegalArgumentException("Invalid value for 'wave.scan.levels' setting - offending value: $value; type: ${value.getClass().getName()}")
}
}
@ToString(includeNames = true, includePackage = false, includeFields = true)
@CompileStatic
class BuildOpts implements ConfigScope {
@ConfigOption
@Description("""
The container repository where images built by Wave are uploaded.
""")
final String repository
@ConfigOption
@Description("""
The container repository used to cache image layers built by the Wave service.
""")
final String cacheRepository
@ConfigOption
@Description("""
The build template to use for container builds. Supported values: `conda/pixi:v1` (Pixi with multi-stage builds), `conda/micromamba:v2` (Micromamba 2.x with multi-stage builds), `cran/installr:v1` (R/CRAN packages). Default: standard conda/micromamba:v1 template.
""")
final String template
final CondaOpts conda
final BuildCompression compression
@ConfigOption
@Description("""
""")
final Duration maxDuration
BuildOpts(Map opts) {
repository = opts.repository
cacheRepository = opts.cacheRepository
template = opts.template
conda = new CondaOpts(opts.conda as Map ?: Collections.emptyMap())
compression = parseCompression(opts.compression as Map)
maxDuration = opts.maxDuration as Duration ?: Duration.of('40m')
}
protected BuildCompression parseCompression(Map opts) {
if( !opts )
return null
final result = new BuildCompression()
if( opts.mode )
result.mode = BuildCompression.Mode.valueOf(opts.mode.toString().toLowerCase())
if( opts.level )
result.level = opts.level as Integer
if( opts.force )
result.force = opts.force as Boolean
return result
}
}