Skip to content

Commit

Permalink
Normalise scan state handling
Browse files Browse the repository at this point in the history
Signed-off-by: Paolo Di Tommaso <[email protected]>
  • Loading branch information
pditommaso committed Sep 25, 2024
1 parent df2b4ac commit 9510722
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ScanConfig {
@Value('${wave.build.workspace}')
private String buildDirectory

@Value('${wave.scan.timeout:10m}')
@Value('${wave.scan.timeout:15m}')
private Duration timeout

@Value('${wave.scan.severity}')
Expand All @@ -72,6 +72,9 @@ class ScanConfig {
@Value('${wave.scan.retry-attempts:1}')
int retryAttempts

@Value('${wave.scan.status.duration:1h}')
Duration statusDuration

String getScanImage() {
return scanImage
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ class ViewController {
binding.scan_status = result.status
binding.scan_failed = result.status == ScanResult.FAILED
binding.scan_succeeded = result.status == ScanResult.SUCCEEDED
binding.scan_exitcode = result.exitCode
binding.scan_logs = result.logs

binding.build_id = result.buildId
binding.build_url = "$serverUrl/view/builds/${result.buildId}"
binding.scan_time = formatTimestamp(result.startTime) ?: '-'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import io.seqera.wave.service.job.JobRecord
import io.seqera.wave.service.scan.ScanResult
import io.seqera.wave.service.scan.ScanVulnerability
import io.seqera.wave.util.StringUtils
Expand All @@ -38,7 +37,7 @@ import io.seqera.wave.util.StringUtils
@ToString(includeNames = true, includePackage = false)
@EqualsAndHashCode
@CompileStatic
class WaveScanRecord implements JobRecord {
class WaveScanRecord {

String id
String buildId
Expand All @@ -48,11 +47,6 @@ class WaveScanRecord implements JobRecord {
String status
List<ScanVulnerability> vulnerabilities

@Override
boolean done() {
return duration != null
}

/* required by jackson deserialization - do not remove */
WaveScanRecord() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package io.seqera.wave.service.scan

import java.nio.file.NoSuchFileException
import java.time.Instant
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService

Expand All @@ -32,8 +31,8 @@ import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.service.builder.BuildEvent
import io.seqera.wave.service.job.JobHandler
import io.seqera.wave.service.job.JobService
import io.seqera.wave.service.job.JobState
import io.seqera.wave.service.job.JobSpec
import io.seqera.wave.service.job.JobState
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.service.persistence.WaveScanRecord
import jakarta.inject.Inject
Expand All @@ -50,7 +49,7 @@ import static io.seqera.wave.service.builder.BuildFormat.DOCKER
@Requires(property = 'wave.scan.enabled', value = 'true')
@Singleton
@CompileStatic
class ContainerScanServiceImpl implements ContainerScanService, JobHandler<WaveScanRecord> {
class ContainerScanServiceImpl implements ContainerScanService, JobHandler<ScanResult> {

@Inject
private ScanConfig scanConfig
Expand All @@ -59,6 +58,9 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<WaveS
@Named(TaskExecutors.IO)
private ExecutorService executor

@Inject
private ScanStateStore scanStore

@Inject
private PersistenceService persistenceService

Expand Down Expand Up @@ -88,7 +90,10 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<WaveS
@Override
WaveScanRecord getScanResult(String scanId) {
try{
return persistenceService.loadScanRecord(scanId)
final scan = scanStore.getScan(scanId)
return scan
? new WaveScanRecord(scan.id, scan)
: persistenceService.loadScanRecord(scanId)
}
catch (Throwable t){
log.error("Unable to load the scan result - id=${scanId}", t)
Expand All @@ -99,7 +104,8 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<WaveS
protected void launch(ScanRequest request) {
try {
// create a record to mark the beginning
persistenceService.createScanRecord(new WaveScanRecord(request.id, request.buildId, request.targetImage, Instant.now()))
final scan = ScanResult.pending(request.id, request.buildId, request.targetImage)
scanStore.put(scan.id, scan)
//launch container scan
jobService.launchScan(request)
}
Expand All @@ -115,50 +121,52 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<WaveS
// **************************************************************

@Override
WaveScanRecord getJobRecord(JobSpec job) {
persistenceService.loadScanRecord(job.recordId)
ScanResult getJobRecord(JobSpec job) {
scanStore.getScan(job.recordId)
}

@Override
void onJobCompletion(JobSpec job, WaveScanRecord scan, JobState state) {
void onJobCompletion(JobSpec job, ScanResult scan, JobState state) {
ScanResult result
if( state.completed() ) {
if( state.succeeded() ) {
try {
result = ScanResult.success(scan, TrivyResultProcessor.process(job.workDir.resolve(Trivy.OUTPUT_FILE_NAME)))
result = scan.success(TrivyResultProcessor.process(job.workDir.resolve(Trivy.OUTPUT_FILE_NAME)))
log.info("Container scan succeeded - id=${scan.id}; exit=${state.exitCode}; stdout=${state.stdout}")
}
catch (NoSuchFileException e) {
result = ScanResult.failure(scan)
result = scan.failure(0, "No such file: ${e.message}")
log.warn("Container scan failed - id=${scan.id}; exit=${state.exitCode}; stdout=${state.stdout}; exception: NoSuchFile=${e.message}")
}
}
else{
result = ScanResult.failure(scan)
result = scan.failure(state.exitCode, state.stdout)
log.warn("Container scan failed - id=${scan.id}; exit=${state.exitCode}; stdout=${state.stdout}")
}

updateScanRecord(result)
}

@Override
void onJobException(JobSpec job, WaveScanRecord scan, Throwable e) {
void onJobException(JobSpec job, ScanResult scan, Throwable e) {
log.error("Container scan exception - id=${scan.id} - cause=${e.getMessage()}", e)
updateScanRecord(ScanResult.failure(scan))
updateScanRecord(scan.failure(null, e.message))
}

@Override
void onJobTimeout(JobSpec job, WaveScanRecord scan) {
void onJobTimeout(JobSpec job, ScanResult scan) {
log.warn("Container scan timed out - id=${scan.id}")
updateScanRecord(ScanResult.failure(scan))
updateScanRecord(scan.failure(null, "Container scan timed out"))
}

protected void updateScanRecord(ScanResult result) {
protected void updateScanRecord(ScanResult scan) {
try{
//save scan results
persistenceService.updateScanRecord(new WaveScanRecord(result.id, result))
//save scan results in the redis cache
scanStore.put(scan.id, scan)
// save in the persistent layer
persistenceService.updateScanRecord(new WaveScanRecord(scan.id, scan))
}
catch (Throwable t){
log.error("Unable to save result - id=${result.id}; cause=${t.message}", t)
log.error("Unable to save result - id=${scan.id}; cause=${t.message}", t)
}
}
}
49 changes: 33 additions & 16 deletions src/main/groovy/io/seqera/wave/service/scan/ScanResult.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ import java.time.Instant

import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.ToString

/**
* Model for scan result
*
* @author Munish Chouhan <[email protected]>
*/

import groovy.transform.ToString
import io.seqera.wave.service.persistence.WaveScanRecord
import io.seqera.wave.service.cache.StateRecord
import io.seqera.wave.service.job.JobRecord

@ToString(includePackage = false, includeNames = true)
@Canonical
@CompileStatic
class ScanResult {
class ScanResult implements StateRecord, JobRecord {

static final public String PENDING = 'PENDING'
static final public String SUCCEEDED = 'SUCCEEDED'
static final public String FAILED = 'FAILED'

Expand All @@ -48,33 +50,48 @@ class ScanResult {
Duration duration
String status
List<ScanVulnerability> vulnerabilities
Integer exitCode
String logs

@Override
String getRecordId() {
return id
}

private ScanResult(String id, String buildId, String containerImage, Instant startTime, Duration duration, String status, List<ScanVulnerability> vulnerabilities) {
this.id = id
this.buildId = buildId
this.containerImage = containerImage
this.startTime = startTime
this.duration = duration
this.status = status
this.vulnerabilities = vulnerabilities
@Override
boolean done() {
return duration != null
}

boolean isSucceeded() { status==SUCCEEDED }

boolean isCompleted() { duration!=null }
@Deprecated
boolean isCompleted() { done() }

static ScanResult success(WaveScanRecord scan, List<ScanVulnerability> vulnerabilities){
return new ScanResult(scan.id, scan.buildId, scan.containerImage, scan.startTime, Duration.between(scan.startTime, Instant.now()), SUCCEEDED, vulnerabilities)
ScanResult success(List<ScanVulnerability> vulnerabilities){
return new ScanResult(
this.id,
this.buildId,
this.containerImage,
this.startTime,
Duration.between(this.startTime, Instant.now()),
SUCCEEDED,
vulnerabilities,
0 )
}

static ScanResult failure(WaveScanRecord scan){
return new ScanResult(scan.id, scan.buildId, scan.containerImage, scan.startTime, Duration.between(scan.startTime, Instant.now()), FAILED, List.of())
ScanResult failure(Integer exitCode, String logs){
return new ScanResult(this.id, this.buildId, this.containerImage, this.startTime, Duration.between(this.startTime, Instant.now()), FAILED, List.of(), exitCode, logs)
}

static ScanResult failure(ScanRequest request){
return new ScanResult(request.id, request.buildId, request.targetImage, request.creationTime, Duration.between(request.creationTime, Instant.now()), FAILED, List.of())
}

static ScanResult pending(String scanId, String buildId, String containerImage) {
return new ScanResult(scanId, buildId, containerImage, Instant.now(), null, PENDING, null)
}

static ScanResult create(String scanId, String buildId, String containerImage, Instant startTime, Duration duration1, String status, List<ScanVulnerability> vulnerabilities){
return new ScanResult(scanId, buildId, containerImage, startTime, duration1, status, vulnerabilities)
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/io/seqera/wave/scan-view.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@
<td>{{scan_duration}}</td>
</tr>

{{#if scan_exitcode}}
<tr>
<td>Exit status</td>
<td>{{scan_exitcode}}</td>
</tr>
{{/if}}

{{#if scan_logs}}
<tr>
<td colspan="2">
<h3>Logs</h3>
<pre style="white-space: pre-wrap; overflow: visible; background-color: #ededed; padding: 15px; border-radius: 4px; margin-bottom:30px;">{{scan_logs}}</pre>
</td>
</tr>
{{/if}}

{{#if scan_succeeded}}
<tr>
<td colspan="2">
Expand Down

0 comments on commit 9510722

Please sign in to comment.