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 mongodb support in wave #572

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ dependencies {
implementation "io.micronaut:micronaut-management"
//views
implementation("io.micronaut.views:micronaut-views-handlebars")
//mongodb
implementation "io.micronaut.mongodb:micronaut-mongo-sync"
}

application {
Expand Down
47 changes: 47 additions & 0 deletions src/main/groovy/io/seqera/wave/configuration/MongoDBConfig.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Wave, containers provisioning service
* Copyright (c) 2023-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.configuration

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Value
import jakarta.inject.Singleton
/**
* MongoDB configuration
*
* @author Munish Chouhan <[email protected]>
*/
@CompileStatic
@Singleton
@Slf4j
class MongoDBConfig {

/**
* MongoDB database name
*/
@Value('${mongodb.database.name}')
String databaseName

/**
* MongoDB uri
*/
@Value('${mongodb.uri}')
String uri

}
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ class ContainerController {

protected void storeContainerRequest0(SubmitContainerTokenRequest req, ContainerRequestData data, TokenData token, String target, String ip) {
try {
final recrd = new WaveContainerRecord(req, data, target, ip, token.expiration)
final recrd = new WaveContainerRecord(req, token, data, target, ip)
persistenceService.saveContainerRequest(token.value, recrd)
}
catch (Throwable e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ package io.seqera.wave.service.persistence
import java.time.Duration
import java.time.Instant

import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import io.micronaut.data.annotation.MappedEntity
import io.seqera.wave.service.builder.BuildEvent
import io.seqera.wave.service.builder.BuildFormat
import io.seqera.wave.api.BuildStatusResponse
import jakarta.persistence.Id

/**
* A collection of request and response properties to be stored
Expand All @@ -37,8 +40,11 @@ import io.seqera.wave.api.BuildStatusResponse
@ToString
@CompileStatic
@EqualsAndHashCode
@MappedEntity
@Canonical
class WaveBuildRecord {

@Id
String buildId
String dockerFile
String condaFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import io.micronaut.data.annotation.MappedEntity
import io.seqera.wave.api.ContainerConfig
import io.seqera.wave.api.FusionVersion
import io.seqera.wave.api.SubmitContainerTokenRequest
import io.seqera.wave.service.ContainerRequestData
import io.seqera.wave.service.token.TokenData
import io.seqera.wave.tower.User
import jakarta.persistence.Id
import static io.seqera.wave.util.DataTimeUtils.parseOffsetDateTime
/**
* Model a Wave container request record
Expand All @@ -40,8 +42,12 @@ import static io.seqera.wave.util.DataTimeUtils.parseOffsetDateTime
@ToString(includeNames = true, includePackage = false)
@Canonical
@CompileStatic
@MappedEntity
class WaveContainerRecord {

@Id
final String token;

/**
* The Tower user associated with the request
*/
Expand Down Expand Up @@ -158,7 +164,8 @@ class WaveContainerRecord {
*/
final String fusionVersion

WaveContainerRecord(SubmitContainerTokenRequest request, ContainerRequestData data, String waveImage, String addr, Instant expiration) {
WaveContainerRecord(SubmitContainerTokenRequest request, TokenData token, ContainerRequestData data, String waveImage, String addr) {
this.token = token.value
this.user = data.identity.user
this.workspaceId = request.towerWorkspaceId
this.containerImage = request.containerImage
Expand All @@ -172,7 +179,7 @@ class WaveContainerRecord {
this.containerFile = data.containerFile
this.sourceImage = data.containerImage
this.waveImage = waveImage
this.expiration = expiration
this.expiration = token.expiration
this.ipAddress = addr
final ts = parseOffsetDateTime(request.timestamp) ?: OffsetDateTime.now()
this.timestamp = ts?.toInstant()
Expand All @@ -184,6 +191,7 @@ class WaveContainerRecord {
}

WaveContainerRecord(WaveContainerRecord that, String sourceDigest, String waveDigest) {
this.token = that.token
this.user = that.user
this.workspaceId = that.workspaceId
this.containerImage = that.containerImage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ package io.seqera.wave.service.persistence
import java.time.Duration
import java.time.Instant

import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import io.micronaut.data.annotation.MappedEntity
import io.seqera.wave.service.scan.ScanResult
import io.seqera.wave.service.scan.ScanVulnerability
import io.seqera.wave.util.StringUtils
import jakarta.persistence.Id

/**
* Model a Wave container scan result
*
Expand All @@ -37,7 +41,10 @@ import io.seqera.wave.util.StringUtils
@ToString(includeNames = true, includePackage = false)
@EqualsAndHashCode
@CompileStatic
@MappedEntity
@Canonical
class WaveScanRecord {
@Id
String id
String buildId
Instant startTime
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.impl

import com.mongodb.client.MongoClient
import com.mongodb.client.MongoCollection
import com.mongodb.client.model.Filters
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Primary
import io.micronaut.context.annotation.Requires
import io.seqera.wave.configuration.MongoDBConfig
import io.seqera.wave.core.ContainerDigestPair
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.service.persistence.WaveBuildRecord
import io.seqera.wave.service.persistence.WaveContainerRecord
import io.seqera.wave.service.persistence.WaveScanRecord
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.bson.Document
/**
* Implements a persistence service based based on SurrealDB
*
* @author : Munish Chouhan <[email protected]>
*/
@Requires(env='mongodb')
@Primary
@Slf4j
@Singleton
@CompileStatic
class MongoDBPersistenceService implements PersistenceService {

@Inject
private MongoClient mongoClient

@Inject
private MongoDBConfig mongoDBConfig

private final String WAVE_BUILD_COLLECTION = "wave_build"
private String WAVE_CONTAINER_COLLECTION = "wave_request"
private String WAVE_SCAN_COLLECTION = "wave_scan"

@Override
void saveBuild(WaveBuildRecord build) {
try {
def collection = getCollection(WAVE_BUILD_COLLECTION, WaveBuildRecord.class)
collection.insertOne(build);
log.trace("Build request with id '{}' saved record: {}", build.getBuildId(), build);
} catch (Exception e) {
log.error("Error saving Build request record {}: {}\n{}", e.getMessage(), build, e);
}
}

@Override
WaveBuildRecord loadBuild(String buildId) {
try {
def collection = getCollection(WAVE_BUILD_COLLECTION, WaveBuildRecord.class)
return collection.find(Filters.eq("_id", buildId)).first()
} catch (Exception e) {
log.error("Error fetching Build request record {}: {}", e.getMessage(), e);
}
return null
}

@Override
WaveBuildRecord loadBuild(String targetImage, String digest) {
return null
}

@Override
void saveContainerRequest(String token, WaveContainerRecord data) {
try {
def collection = getCollection(WAVE_CONTAINER_COLLECTION, WaveContainerRecord.class)
collection.insertOne(data);
log.trace("Container request with id '{}' saved record: {}", data.token, data);
} catch (Exception e) {
log.error("Error saving container request record {}: {}\n{}", e.getMessage(), data, e);
}
}

@Override
void updateContainerRequest(String token, ContainerDigestPair digest) {
try {
def collection = getCollection(WAVE_CONTAINER_COLLECTION, WaveContainerRecord.class)
collection.updateOne(Filters.eq("token", token), new Document("\$set", new Document("sourceDigest", digest.source).append("waveDigest", digest.target)))
log.trace("Container request with id '{}' updated digest: {}", token, digest);
} catch (Exception e) {
log.error("Error updating Container request record {}: {}\n{}", e.getMessage(), token, e);
}
}

@Override
WaveContainerRecord loadContainerRequest(String token) {
try {
def collection = getCollection(WAVE_CONTAINER_COLLECTION, WaveContainerRecord.class)
return collection.find(Filters.eq("_id", token)).first()
} catch (Exception e) {
log.error("Error fetching container request record {}: {}", e.getMessage(), e);
}
return null
}

@Override
void createScanRecord(WaveScanRecord scanRecord) {
try {
def collection = getCollection(WAVE_SCAN_COLLECTION, WaveScanRecord.class)
collection.insertOne(scanRecord);
log.trace("Container scan with id '{}' saved record: {}", scanRecord.id, scanRecord);
} catch (Exception e) {
log.error("Error saving container scan record {}: {}\n{}", e.getMessage(), scanRecord, e);
}
}

@Override
void updateScanRecord(WaveScanRecord scanRecord) {

}

@Override
WaveScanRecord loadScanRecord(String scanId) {
try {
def collection = getCollection(WAVE_SCAN_COLLECTION, WaveScanRecord.class)
return collection.find(Filters.eq("_id", scanId)).first()
} catch (Exception e) {
log.error("Error fetching container scan record {}: {}", e.getMessage(), e);
}
return null
}

private <T> MongoCollection<T> getCollection(String collectionName, Class<T> type) {
return mongoClient.getDatabase(mongoDBConfig.databaseName).getCollection(collectionName, type)
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application-mongodb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mongodb:
uri: mongodb://${MONGODB_USERNAME:''}:${MONGODB_PASSWORD:''}@${MONGODB_HOST:localhost}:${MONGODB_PORT:27017}/${MONGODB_NAME:wave}
database:
name: ${MONGODB_NAME:wave}
Loading
Loading