Skip to content

Commit cf8dda0

Browse files
committed
Initial commit
Signed-off-by: munishchouhan <hrma017@gmail.com>
1 parent ccfc943 commit cf8dda0

28 files changed

Lines changed: 371 additions & 507 deletions

src/main/groovy/io/seqera/wave/configuration/BuildConfig.groovy

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,8 @@ class BuildConfig {
5454
@Value('${wave.build.public-repo}')
5555
String defaultPublicRepository
5656

57-
/**
58-
* File system path there the dockerfile is save
59-
*/
60-
@Value('${wave.build.workspace}')
61-
String buildWorkspace
57+
@Value('${wave.build.workspace-bucket}')
58+
String workspaceBucket
6259

6360
@Value('${wave.build.status.delay}')
6461
Duration statusDelay
@@ -140,7 +137,7 @@ class BuildConfig {
140137
"default-build-repository=${defaultBuildRepository}; " +
141138
"default-cache-repository=${defaultCacheRepository}; " +
142139
"default-public-repository=${defaultPublicRepository}; " +
143-
"build-workspace=${buildWorkspace}; " +
140+
"build-workspace-bucket=${workspaceBucket}; " +
144141
"build-timeout=${defaultTimeout}; " +
145142
"build-trusted-timeout=${trustedTimeout}; " +
146143
"build-logs-path=${logsPath}; " +
@@ -200,4 +197,21 @@ class BuildConfig {
200197
final store = BucketTokenizer.from(locksPath)
201198
return store.scheme ? store.getKey() : null
202199
}
200+
201+
/**
202+
* The file name prefix applied when storing a build workspace file into an object storage.
203+
* For example having {@link #workspaceBucket} as {@code s3://bucket-name/foo/bar} the
204+
* value returned by this method is {@code foo/bar}.
205+
*
206+
* When using a local path the prefix is {@code null}.
207+
*
208+
* @return the workspace file name prefix
209+
*/
210+
@Memoized
211+
String getWorkspacePrefix() {
212+
if( !workspaceBucket )
213+
return null
214+
final store = BucketTokenizer.from(workspaceBucket)
215+
return store.scheme ? store.getKey() : null
216+
}
203217
}

src/main/groovy/io/seqera/wave/configuration/ScanConfig.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ class ScanConfig {
112112
}
113113

114114
@Memoized
115-
Path getWorkspace() {
116-
Path.of(buildDirectory).toAbsolutePath()
115+
String getWorkspace() {
116+
return buildDirectory
117117
}
118118

119119
String getRequestsCpu() {

src/main/groovy/io/seqera/wave/controller/ContainerController.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ class ContainerController {
361361
containerId,
362362
containerSpec,
363363
condaContent,
364-
Path.of(buildConfig.buildWorkspace),
364+
buildConfig.workspaceBucket,
365365
targetImage,
366366
identity,
367367
platform,
@@ -521,7 +521,7 @@ class ContainerController {
521521
targetImage,
522522
digest,
523523
null, // <-- use null to copy all container tags
524-
Path.of(buildConfig.buildWorkspace).toAbsolutePath(),
524+
buildConfig.workspaceBucket,
525525
configJson,
526526
scanId,
527527
Instant.now(),

src/main/groovy/io/seqera/wave/service/aws/ObjectStorageOperationsFactory.groovy

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import groovy.transform.CompileStatic
2424
import groovy.util.logging.Slf4j
2525
import io.micronaut.context.ApplicationContext
2626
import io.micronaut.context.annotation.Factory
27+
import io.micronaut.context.annotation.Requires
2728
import io.micronaut.inject.qualifiers.Qualifiers
2829
import io.micronaut.objectstorage.InputStreamMapper
2930
import io.micronaut.objectstorage.ObjectStorageOperations
@@ -52,13 +53,23 @@ class ObjectStorageOperationsFactory {
5253

5354
public static final String BUILD_LOCKS = "build-locks"
5455

56+
public static final String BUILD_WORKSPACE = "build-workspace"
57+
5558
@Inject
5659
private ApplicationContext context
5760

5861
@Inject
5962
@Nullable
6063
private BuildConfig buildConfig
6164

65+
@Singleton
66+
@Named(BUILD_WORKSPACE)
67+
ObjectStorageOperations<?, ?, ?> createBuildWorkspaceOps() {
68+
if( !buildConfig )
69+
throw new IllegalStateException("Build configuration is not defined")
70+
return create0(BUILD_WORKSPACE, buildConfig.workspaceBucket, "wave.build.workspace-bucket")
71+
}
72+
6273
@Singleton
6374
@Named(BUILD_LOGS)
6475
ObjectStorageOperations<?, ?, ?> createLogsStorageOps() {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Wave, containers provisioning service
3+
* Copyright (c) 2023-2024, Seqera Labs
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package io.seqera.wave.service.builder
20+
21+
/**
22+
* Constants for the build service
23+
*
24+
* @author Munish Chouhan <munish.chouhan@seqera.io>
25+
*/
26+
class BuildConstants {
27+
28+
public static final String FUSION_PREFIX = "/fusion/s3"
29+
30+
static final public String BUILDKIT_ENTRYPOINT = 'buildctl-daemonless.sh'
31+
}

src/main/groovy/io/seqera/wave/service/builder/BuildRequest.groovy

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class BuildRequest {
6666
/**
6767
* The build context work directory
6868
*/
69-
final Path workspace
69+
final String workspace
7070

7171
/**
7272
* The target fully qualified image of the built container. It includes the target registry name
@@ -147,7 +147,7 @@ class BuildRequest {
147147
String containerId,
148148
String containerFile,
149149
String condaFile,
150-
Path workspace,
150+
String workspace,
151151
String targetImage,
152152
PlatformId identity,
153153
ContainerPlatform platform,
@@ -189,7 +189,7 @@ class BuildRequest {
189189
this.containerId = opts.containerId
190190
this.containerFile = opts.containerFile
191191
this.condaFile = opts.condaFile
192-
this.workspace = opts.workspace as Path
192+
this.workspace = opts.workspace
193193
this.targetImage = opts.targetImage
194194
this.identity = opts.identity as PlatformId
195195
this.platform = opts.platform as ContainerPlatform
@@ -229,10 +229,6 @@ class BuildRequest {
229229
return condaFile
230230
}
231231

232-
Path getWorkDir() {
233-
return workspace.resolve(buildId).toAbsolutePath()
234-
}
235-
236232
String getTargetImage() {
237233
return targetImage
238234
}

src/main/groovy/io/seqera/wave/service/builder/BuildStrategy.groovy

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
package io.seqera.wave.service.builder
2020

2121
import groovy.transform.CompileStatic
22+
import io.micronaut.objectstorage.ObjectStorageOperations
2223
import io.seqera.wave.configuration.BuildConfig
2324
import jakarta.inject.Inject
25+
import jakarta.inject.Named
26+
import static io.seqera.wave.service.aws.ObjectStorageOperationsFactory.BUILD_WORKSPACE
27+
import static io.seqera.wave.service.builder.BuildConstants.FUSION_PREFIX
2428
/**
2529
* Defines an abstract container build strategy.
2630
*
@@ -39,7 +43,9 @@ abstract class BuildStrategy {
3943

4044
abstract List<String> singularityLaunchCmd(BuildRequest req)
4145

42-
static final public String BUILDKIT_ENTRYPOINT = 'buildctl-daemonless.sh'
46+
@Inject
47+
@Named(BUILD_WORKSPACE)
48+
private ObjectStorageOperations<?, ?, ?> objectStorageOperations
4349

4450
List<String> launchCmd(BuildRequest req) {
4551
if(req.formatDocker()) {
@@ -59,11 +65,11 @@ abstract class BuildStrategy {
5965
<< "--frontend"
6066
<< "dockerfile.v0"
6167
<< "--local"
62-
<< "dockerfile=$req.workDir".toString()
68+
<< "dockerfile=$FUSION_PREFIX/$buildConfig.workspaceBucket/$req.workspace".toString()
6369
<< "--opt"
6470
<< "filename=Containerfile"
6571
<< "--local"
66-
<< "context=$req.workDir/context".toString()
72+
<< "context=$FUSION_PREFIX/$buildConfig.workspaceBucket/$req.workspace/context".toString()
6773
<< "--output"
6874
<< outputOpts(req, buildConfig)
6975
<< "--opt"
@@ -120,4 +126,10 @@ abstract class BuildStrategy {
120126
return result.toString()
121127
}
122128

129+
String getSymlinkSingularity( BuildRequest req ) {
130+
if( req.configJson ){
131+
return "ln -s $FUSION_PREFIX/$buildConfig.workspaceBucket/$req.workspace/.singularity /root/.singularity &&"
132+
}
133+
return ""
134+
}
123135
}

src/main/groovy/io/seqera/wave/service/builder/DockerBuildStrategy.groovy

Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,18 @@
1818

1919
package io.seqera.wave.service.builder
2020

21-
import java.nio.file.Files
22-
import java.nio.file.Path
23-
2421
import groovy.transform.CompileStatic
2522
import groovy.util.logging.Slf4j
2623
import io.micronaut.context.annotation.Value
24+
import io.micronaut.objectstorage.ObjectStorageOperations
25+
import io.micronaut.objectstorage.request.UploadRequest
2726
import io.seqera.wave.configuration.BuildConfig
28-
import io.seqera.wave.core.ContainerPlatform
2927
import io.seqera.wave.core.RegistryProxyService
3028
import jakarta.inject.Inject
29+
import jakarta.inject.Named
3130
import jakarta.inject.Singleton
32-
import static java.nio.file.StandardOpenOption.CREATE
33-
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING
34-
import static java.nio.file.StandardOpenOption.WRITE
31+
import static io.seqera.wave.service.aws.ObjectStorageOperationsFactory.BUILD_WORKSPACE
32+
import static io.seqera.wave.service.builder.BuildConstants.FUSION_PREFIX
3533
/**
3634
* Build a container image using a Docker CLI tool
3735
*
@@ -51,66 +49,63 @@ class DockerBuildStrategy extends BuildStrategy {
5149
@Inject
5250
RegistryProxyService proxyService
5351

52+
@Inject
53+
@Named(BUILD_WORKSPACE)
54+
private ObjectStorageOperations<?, ?, ?> objectStorageOperations
55+
5456
@Override
5557
void build(String jobName, BuildRequest req) {
5658

57-
final Path configFile = req.configJson ? req.workDir.resolve('config.json') : null
5859
// command the docker build command
59-
final buildCmd= buildCmd(jobName, req, configFile)
60+
final buildCmd= buildCmd(jobName, req)
6061
log.debug "Build run command: ${buildCmd.join(' ')}"
6162
// save docker cli for debugging purpose
6263
if( debug ) {
63-
Files.write(req.workDir.resolve('docker.sh'),
64-
cmdToStr(buildCmd).bytes,
65-
CREATE, WRITE, TRUNCATE_EXISTING)
64+
objectStorageOperations.upload(UploadRequest.fromBytes(buildCmd.join(' ').bytes, "$req.workspace/docker.sh".toString()))
6665
}
6766

68-
final process = new ProcessBuilder()
67+
final builder = new ProcessBuilder()
6968
.command(buildCmd)
70-
.directory(req.workDir.toFile())
71-
.redirectErrorStream(true)
72-
.start()
69+
//this is to run it in windows
70+
.redirectError(ProcessBuilder.Redirect.INHERIT)
71+
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
72+
73+
def process = builder.start()
7374

7475
if( process.waitFor()!=0 ) {
7576
throw new IllegalStateException("Unable to launch build container - exitCode=${process.exitValue()}; output=${process.text}")
7677
}
7778
}
7879

79-
private String cmdToStr(List<String> cmd) {
80-
return cmd
81-
.collect(it-> !it || it.contains(' ') ? "\"$it\"".toString() : it)
82-
.collect(it-> it.startsWith('-') ? "\\\n $it".toString() : it)
83-
.join(' ')
84-
}
85-
86-
protected List<String> buildCmd(String jobName, BuildRequest req, Path credsFile) {
80+
protected List<String> buildCmd(String jobName, BuildRequest req) {
8781

8882
final dockerCmd = req.formatDocker()
89-
? cmdForBuildkit(jobName, req.workDir, credsFile, req.platform)
90-
: cmdForSingularity(jobName, req.workDir, credsFile, req.platform)
83+
? cmdForBuildkit(jobName, req)
84+
: cmdForSingularity(jobName, req)
9185

9286
return dockerCmd + launchCmd(req)
9387
}
9488

95-
protected List<String> cmdForBuildkit(String name, Path workDir, Path credsFile, ContainerPlatform platform ) {
89+
protected List<String> cmdForBuildkit(String name, BuildRequest req) {
9690
//checkout the documentation here to know more about these options https://github.com/moby/buildkit/blob/master/docs/rootless.md#docker
9791
final wrapper = ['docker',
9892
'run',
9993
'--detach',
10094
'--name', name,
10195
'--privileged',
102-
'-v', "$workDir:$workDir".toString(),
103-
'--entrypoint',
104-
BUILDKIT_ENTRYPOINT]
105-
106-
if( credsFile ) {
107-
wrapper.add('-v')
108-
wrapper.add("$credsFile:/home/user/.docker/config.json:ro".toString())
96+
'-e',
97+
"AWS_ACCESS_KEY_ID=${System.getenv('AWS_ACCESS_KEY_ID')}".toString(),
98+
'-e',
99+
"AWS_SECRET_ACCESS_KEY=${System.getenv('AWS_SECRET_ACCESS_KEY')}".toString()]
100+
101+
if( req.configJson ) {
102+
wrapper.add('-e')
103+
wrapper.add("DOCKER_CONFIG=$FUSION_PREFIX/$buildConfig.workspaceBucket/$req.workspace".toString())
109104
}
110105

111-
if( platform ) {
106+
if( req.platform ) {
112107
wrapper.add('--platform')
113-
wrapper.add(platform.toString())
108+
wrapper.add(req.platform.toString())
114109
}
115110

116111
// the container image to be used to build
@@ -119,26 +114,20 @@ class DockerBuildStrategy extends BuildStrategy {
119114
return wrapper
120115
}
121116

122-
protected List<String> cmdForSingularity(String name, Path workDir, Path credsFile, ContainerPlatform platform) {
117+
protected List<String> cmdForSingularity(String name, BuildRequest req) {
123118
final wrapper = ['docker',
124119
'run',
125120
'--detach',
126121
'--name', name,
127122
'--privileged',
128-
"--entrypoint", '',
129-
'-v', "$workDir:$workDir".toString()]
130-
131-
if( credsFile ) {
132-
wrapper.add('-v')
133-
wrapper.add("$credsFile:/root/.singularity/docker-config.json:ro".toString())
134-
//
135-
wrapper.add('-v')
136-
wrapper.add("${credsFile.resolveSibling('singularity-remote.yaml')}:/root/.singularity/remote.yaml:ro".toString())
137-
}
123+
'-e',
124+
"AWS_ACCESS_KEY_ID=${System.getenv('AWS_ACCESS_KEY_ID')}".toString(),
125+
'-e',
126+
"AWS_SECRET_ACCESS_KEY=${System.getenv('AWS_SECRET_ACCESS_KEY')}".toString()]
138127

139-
if( platform ) {
128+
if( req.platform ) {
140129
wrapper.add('--platform')
141-
wrapper.add(platform.toString())
130+
wrapper.add(req.platform.toString())
142131
}
143132

144133
wrapper.add(buildConfig.singularityImage)
@@ -150,7 +139,8 @@ class DockerBuildStrategy extends BuildStrategy {
150139
result
151140
<< 'sh'
152141
<< '-c'
153-
<< "singularity build image.sif ${req.workDir}/Containerfile && singularity push image.sif ${req.targetImage}".toString()
142+
<< """${getSymlinkSingularity(req)} singularity build image.sif $FUSION_PREFIX/$buildConfig.workspaceBucket/$req.workspace/Containerfile \
143+
&& singularity push image.sif ${req.targetImage}""".toString()
154144
return result
155145
}
156146
}

0 commit comments

Comments
 (0)