Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for conda lock file #642

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
96518c7
[release] bump 1.12.0-B4
munishchouhan Sep 11, 2024
1c9ba7f
initial commit
munishchouhan Sep 12, 2024
86a70b2
Revert "[release] bump 1.12.0-B4"
munishchouhan Sep 12, 2024
cb3d544
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 12, 2024
50fb94b
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 13, 2024
a929e4a
added check
munishchouhan Sep 13, 2024
55aa1b1
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 16, 2024
0ae7134
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 17, 2024
4b35971
removed s3 and added idb support for CondaLockService
munishchouhan Sep 17, 2024
85076c1
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 17, 2024
46e72f0
changed condalock datatype to byte[]
munishchouhan Sep 17, 2024
644cbb4
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 17, 2024
2895afe
added removeCondaLockFile
munishchouhan Sep 17, 2024
fe78d18
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 18, 2024
1f55209
revert unwanted changes
munishchouhan Sep 18, 2024
6bd2315
removed application-dev-conda.yml
munishchouhan Sep 18, 2024
92d28e3
added tests
munishchouhan Sep 18, 2024
3d0c7c4
added and fixed tests
munishchouhan Sep 18, 2024
2a675f7
added conda lockfile name
munishchouhan Sep 18, 2024
f1dafa3
removed @Nullable
munishchouhan Sep 18, 2024
b7123f9
doc correction [ci skip]
munishchouhan Sep 18, 2024
2656843
[release] bump 1.13.0-A1
munishchouhan Sep 18, 2024
0d0b8d7
update VERSION
munishchouhan Sep 18, 2024
fea3891
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 24, 2024
db95ab6
fixed errors
munishchouhan Sep 24, 2024
2f070ad
refactored
munishchouhan Sep 24, 2024
ae2c7bb
refactored
munishchouhan Sep 24, 2024
59fd91d
Update src/main/resources/io/seqera/wave/build-view.hbs
munishchouhan Sep 25, 2024
bd2e110
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 26, 2024
6eee8e1
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 26, 2024
8bbc7ec
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Sep 27, 2024
07651a7
Merge branch 'master' into 172-add-support-for-conda-lock-file
munishchouhan Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/main/groovy/io/seqera/wave/controller/BuildController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package io.seqera.wave.controller
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.core.annotation.Nullable
import io.micronaut.http.HttpHeaders
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
Expand All @@ -32,6 +33,7 @@ import io.micronaut.scheduling.annotation.ExecuteOn
import io.seqera.wave.api.BuildStatusResponse
import io.seqera.wave.exception.BadRequestException
import io.seqera.wave.service.builder.ContainerBuildService
import io.seqera.wave.service.conda.CondaLockService
import io.seqera.wave.service.logs.BuildLogService
import io.seqera.wave.service.mirror.ContainerMirrorService
import io.seqera.wave.service.mirror.MirrorRequest
Expand All @@ -58,6 +60,9 @@ class BuildController {
@Nullable
BuildLogService logService

@Inject
CondaLockService condaLockService

@Get("/v1alpha1/builds/{buildId}")
HttpResponse<WaveBuildRecord> getBuildRecord(String buildId) {
final record = buildService.getBuildRecord(buildId)
Expand Down Expand Up @@ -85,6 +90,18 @@ class BuildController {
: HttpResponse.<BuildStatusResponse>notFound()
}

@Produces(MediaType.TEXT_PLAIN)
@Get(value="/v1alpha1/builds/{buildId}/condalock")
HttpResponse<StreamedFile> getCondaLock(String buildId){
if( condaLockService==null )
throw new IllegalStateException("Build Logs service not configured")
final condaLock = condaLockService.fetchCondaLock(buildId)
return condaLock
? HttpResponse.ok(condaLock)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + buildId + ".lock\"")
: HttpResponse.<StreamedFile>notFound()
}

protected BuildStatusResponse buildResponse0(String buildId) {
if( !buildId )
throw new BadRequestException("Missing 'buildId' parameter")
Expand All @@ -100,4 +117,5 @@ class BuildController {
?.toStatusResponse()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import io.micronaut.scheduling.annotation.ExecuteOn
import io.micronaut.views.View
import io.seqera.wave.exception.NotFoundException
import io.seqera.wave.service.builder.ContainerBuildService
import io.seqera.wave.service.conda.CondaLockService
import io.seqera.wave.service.inspect.ContainerInspectService
import io.seqera.wave.service.logs.BuildLogService
import io.seqera.wave.service.persistence.PersistenceService
Expand Down Expand Up @@ -68,6 +69,9 @@ class ViewController {
@Nullable
private BuildLogService buildLogService

@Inject
private CondaLockService condaLockService

@Inject
private ContainerInspectService inspectService

Expand Down Expand Up @@ -152,6 +156,10 @@ class ViewController {
binding.build_log_truncated = buildLog?.truncated
binding.build_log_url = "$serverUrl/v1alpha1/builds/${result.buildId}/logs"
}
//configure conda lock file when available
if( condaLockService && result.condaFile ) {
binding.build_conda_lock_url = "$serverUrl/v1alpha1/builds/${result.buildId}/condalock"
}
// result the main object
return binding
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/groovy/io/seqera/wave/service/conda/Conda.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.seqera.wave.service.conda

/**
* Interface for Conda constants
*
* @author Munish Chouhan <[email protected]>
*/
interface Conda {

String CONDA_LOCK_START = "conda_lock_start"
String CONDA_LOCK_END = "conda_lock_end"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Wave, containers provisioning service
* Copyright (c) 2024, Seqera Labs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package io.seqera.wave.service.conda

import io.micronaut.http.server.types.files.StreamedFile

/**
* Service to manage conda lock files
*
* @author Munish Chouhan <[email protected]>
*/
interface CondaLockService {

void storeCondaLock(String buildId, String condaLock)

StreamedFile fetchCondaLock(String buildId)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Wave, containers provisioning service
* Copyright (c) 2024, Seqera Labs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package io.seqera.wave.service.conda.impl

import io.micronaut.http.MediaType
import io.micronaut.http.server.types.files.StreamedFile
import io.seqera.wave.service.conda.CondaLockService

import java.nio.charset.StandardCharsets
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.TaskExecutors
import io.seqera.wave.service.builder.BuildEvent
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.service.persistence.WaveCondaLockRecord
import jakarta.inject.Inject
import jakarta.inject.Named
import jakarta.inject.Singleton
import static io.seqera.wave.service.conda.Conda.CONDA_LOCK_END
import static io.seqera.wave.service.conda.Conda.CONDA_LOCK_START
/**
* Implements Service to manage conda lock files from an Object store
*
* @author Munish Chouhan <[email protected]>
*/
@Slf4j
@Singleton
@CompileStatic
class CondaLockServiceImpl implements CondaLockService {

@Inject
@Named(TaskExecutors.IO)
private volatile ExecutorService ioExecutor

@Inject
PersistenceService persistenceService

@EventListener
void onBuildEvent(BuildEvent event) {
if ( event.request.condaFile ) {
CompletableFuture.supplyAsync(() -> storeCondaLock(event.result.id, event.result.logs), ioExecutor)
}
}

@Override
void storeCondaLock(String buildId, String logs) {
if( !logs ) return
try {
String condaLock = extractCondaLockFile(logs)
if (condaLock){
log.debug "Storing condalock for buildId: $buildId"
def record = new WaveCondaLockRecord(buildId, condaLock.getBytes(StandardCharsets.UTF_8))
persistenceService.saveCondaLock(record)
}
}
catch (Exception e) {
log.warn "Unable to store condalock for buildId: $buildId - reason: ${e.message}", e
}
}

@Override
StreamedFile fetchCondaLock(String buildId) {
if( !buildId )
return null
def condaLock = persistenceService.loadCondaLock(buildId)?.condaLockFile
if( !condaLock )
return null
def inputStream = new ByteArrayInputStream(condaLock)
return new StreamedFile(inputStream, MediaType.APPLICATION_OCTET_STREAM_TYPE)

}

protected static extractCondaLockFile(String logs) {
try {
return logs.substring(logs.lastIndexOf(CONDA_LOCK_START) + CONDA_LOCK_START.length(), logs.lastIndexOf(CONDA_LOCK_END))
.replaceAll(/#\d+ \d+\.\d+\s*/, '')
} catch (Exception e) {
log.warn "Unable to extract conda lock file from logs - reason: ${e.message}", e
return null
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import jakarta.inject.Inject
import jakarta.inject.Named
import jakarta.inject.Singleton
import org.apache.commons.io.input.BoundedInputStream
import static org.apache.commons.lang3.StringUtils.strip
import static io.seqera.wave.service.conda.Conda.CONDA_LOCK_END
import static io.seqera.wave.service.conda.Conda.CONDA_LOCK_START
/**
* Implements Service to manage logs from an Object store
*
Expand Down Expand Up @@ -83,7 +86,7 @@ class BuildLogServiceImpl implements BuildLogService {
return null
if( !prefix )
return buildId + '.log'
final base = org.apache.commons.lang3.StringUtils.strip(prefix, '/')
final base = strip(prefix, '/')
return "${base}/${buildId}.log"
}

Expand All @@ -97,6 +100,7 @@ class BuildLogServiceImpl implements BuildLogService {
@Override
void storeLog(String buildId, String content){
try {
content = removeCondaLockFile(content)
pditommaso marked this conversation as resolved.
Show resolved Hide resolved
log.debug "Storing logs for buildId: $buildId"
final uploadRequest = UploadRequest.fromBytes(content.getBytes(), logKey(buildId))
objectStorageOperations.upload(uploadRequest)
Expand Down Expand Up @@ -125,4 +129,11 @@ class BuildLogServiceImpl implements BuildLogService {
final logs = new BoundedInputStream(result.getInputStream(), maxLength).getText()
return new BuildLog(logs, logs.length()>=maxLength)
}

protected static removeCondaLockFile(String logs) {
if(logs.indexOf(CONDA_LOCK_START) < 0 ) {
return logs
}
return logs.replaceAll(/(?s)\n?#\d+ \d+\.\d+ $CONDA_LOCK_START.*?$CONDA_LOCK_END\n?/, '\n')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ interface PersistenceService {
*/
WaveScanRecord loadScanRecord(String scanId)

/**
* Store a condaLock for buildId in the underlying persistence layer.
*
*
* @param build A {@link WaveBuildRecord} object
*/
void saveCondaLock(WaveCondaLockRecord condaLock)

/**
* Retrieve a condaLock for the given build id
*
* @param buildId
* @return The corresponding condaLock file as a string
*/
WaveCondaLockRecord loadCondaLock(String buildId)

/**
* Load a mirror state record
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Wave, containers provisioning service
* Copyright (c) 2024, Seqera Labs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package io.seqera.wave.service.persistence

import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString

/**
* Model a Wave conda lock record
*
* @author Munish Chouhan <[email protected]>
*/
@ToString
@CompileStatic
@EqualsAndHashCode
class WaveCondaLockRecord {
String buildId
byte[] condaLockFile

WaveCondaLockRecord() {}

WaveCondaLockRecord(String buildId, byte[] condaLockFile) {
this.buildId = buildId
this.condaLockFile = condaLockFile
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.seqera.wave.core.ContainerDigestPair
import io.seqera.wave.service.mirror.MirrorEntry
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.service.persistence.WaveBuildRecord
import io.seqera.wave.service.persistence.WaveCondaLockRecord
import io.seqera.wave.service.persistence.WaveContainerRecord
import io.seqera.wave.service.persistence.WaveScanRecord
import jakarta.inject.Singleton
Expand All @@ -38,9 +39,12 @@ class LocalPersistenceService implements PersistenceService {
private Map<String,WaveBuildRecord> buildStore = new HashMap<>()

private Map<String,WaveContainerRecord> requestStore = new HashMap<>()

private Map<String,WaveScanRecord> scanStore = new HashMap<>()
private Map<String, MirrorEntry> mirrorStore = new HashMap<>()

private Map<String,WaveCondaLockRecord> condaLockStore = new HashMap<>()

@Override
void saveBuild(WaveBuildRecord record) {
buildStore[record.buildId] = record
Expand Down Expand Up @@ -98,6 +102,16 @@ class LocalPersistenceService implements PersistenceService {
scanStore.get(scanId)
}

@Override
void saveCondaLock(WaveCondaLockRecord condaLock) {
condaLockStore.put(condaLock.buildId, condaLock)
}

@Override
WaveCondaLockRecord loadCondaLock(String buildId) {
return condaLockStore.get(buildId)
}

MirrorEntry loadMirrorEntry(String mirrorId) {
mirrorStore.get(mirrorId)
}
Expand Down
Loading