Skip to content

Commit

Permalink
feat(Storage): Support using AWS S3 as online cache for scan results
Browse files Browse the repository at this point in the history
Signed-off-by: carlos-ardila2 <[email protected]>
  • Loading branch information
carlos-ardila2 authored and sschuberth committed Oct 19, 2023
1 parent a88c505 commit a5602a2
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 5 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,35 @@ ort:
storageWriters: ["artifactoryStorage"]
```
### AWS S3 Storage
An [AWS S3 Bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) can be used to store scan
results. You must provide a previously created Bucket, an optional AWS region (if not present, the
[default](https://docs.aws.amazon.com/sdk-for-kotlin/latest/developer-guide/region-selection.html) will be used),
credentials (if not provided, system
[default](https://docs.aws.amazon.com/sdk-for-kotlin/latest/developer-guide/credential-providers.html) values will be
used), and an optional [custom endpoint](https://docs.aws.amazon.com/sdkref/latest/guide/feature-ss-endpoints.html) to
support local endpoints, VPC endpoints, and third-party local AWS development environments. Also, you can optionally
set the data to be compressed before stored.
```yaml
ort:
scanner:
storages:
awsS3Storage:
backend:
s3FileStorage:
accessKeyId: "aws-access-key (optional)"
awsRegion: "us-east-1 (optional)"
bucketName: "ort-scan-results"
compression: true (optional),
customEndpoint: "https://192.145.16.241/aws (optional)",
secretAccessKey: "aws-access-key-secret (optional)"

storageReaders: ["awsS3Storage"]
storageWriters: ["awsS3Storage"]
```
### PostgreSQL Storage
To use PostgreSQL for storing scan results you need at least version 9.4, create a database with the `client_encoding`
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ postgresEmbedded = "1.0.2"
reflections = "0.10.2"
retrofit = "2.9.0"
retrofitConverterKotlinxSerialization = "1.0.0"
s3 = "2.20.162"
saxonHe = "12.3"
scanoss = "1.1.6"
semver4j = "5.2.2"
Expand All @@ -74,6 +75,7 @@ versions = { id = "com.github.ben-manes.versions", version.ref = "versionsPlugin
antlr = { module = "org.antlr:antlr4", version.ref = "antlr" }
asciidoctorj = { module = "org.asciidoctor:asciidoctorj", version.ref = "asciidoctorj" }
asciidoctorjPdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorjPdf" }
awsS3 = { module = "software.amazon.awssdk:s3", version.ref = "s3" }
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
commonsCompress = { module = "org.apache.commons:commons-compress", version.ref = "commonsCompress" }
cvssCalculator = { module = "us.springett:cvss-calculator", version.ref = "cvssCalculator" }
Expand Down
31 changes: 31 additions & 0 deletions integrations/schemas/ort-configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@
},
"httpFileStorage": {
"§ref": "#/definitions/HttpFileStorage"
},
"s3FileStorage": {
"§ref": "#/definitions/S3FileStorage"
}
},
"required": [
Expand Down Expand Up @@ -303,6 +306,33 @@
"url"
]
},
"S3FileStorage": {
"type": "object",
"additionalProperties": false,
"properties": {
"accessKeyId": {
"type": "string"
},
"awsRegion": {
"type": "string"
},
"bucketName": {
"type": "string"
},
"compression": {
"type": "boolean"
},
"customEndpoint": {
"type": "string"
},
"secretAccessKey": {
"type": "string"
}
},
"required": [
"bucketName"
]
},
"PostgresConfig": {
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -397,6 +427,7 @@
},
"StorageTypes": {
"enum": [
"aws",
"clearlyDefined",
"http",
"local",
Expand Down
17 changes: 15 additions & 2 deletions model/src/main/kotlin/config/FileStorageConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.ossreviewtoolkit.utils.common.expandTilde
import org.ossreviewtoolkit.utils.ort.storage.FileStorage
import org.ossreviewtoolkit.utils.ort.storage.HttpFileStorage
import org.ossreviewtoolkit.utils.ort.storage.LocalFileStorage
import org.ossreviewtoolkit.utils.ort.storage.S3FileStorage
import org.ossreviewtoolkit.utils.ort.storage.XZCompressedLocalFileStorage

/**
Expand All @@ -37,20 +38,32 @@ data class FileStorageConfiguration(
/**
* The configuration of a [LocalFileStorage].
*/
val localFileStorage: LocalFileStorageConfiguration? = null
val localFileStorage: LocalFileStorageConfiguration? = null,

/**
* The configuration of a [S3FileStorage].
*/
val s3FileStorage: S3FileStorageConfiguration? = null
) {
/**
* Create a [FileStorage] based on this configuration.
*/
fun createFileStorage(): FileStorage {
val storage = requireNotNull(listOfNotNull(httpFileStorage, localFileStorage).singleOrNull()) {
val storage = requireNotNull(listOfNotNull(httpFileStorage, localFileStorage, s3FileStorage).singleOrNull()) {
"Exactly one implementation must be configured for a FileStorage."
}

if (storage is HttpFileStorageConfiguration) {
return HttpFileStorage(storage.url, storage.query, storage.headers)
}

if (storage is S3FileStorageConfiguration) {
return S3FileStorage(
storage.accessKeyId, storage.awsRegion, storage.bucketName, storage.compression,
storage.customEndpoint, storage.secretAccessKey
)
}

check(storage is LocalFileStorageConfiguration)

val directory = storage.directory.expandTilde()
Expand Down
43 changes: 43 additions & 0 deletions model/src/main/kotlin/config/S3FileStorageConfiguration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* 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
*
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.ossreviewtoolkit.model.config

/**
* A class to hold the configuration for using an AWS S3 bucket as a storage.
*/
data class S3FileStorageConfiguration(
/** The AWS access key */
val accessKeyId: String? = null,

/** The AWS region to be used. */
val awsRegion: String? = null,

/** The name of the S3 bucket used to store files in. */
val bucketName: String,

/** Whether to use compression for storing files or not. Defaults to true. */
val compression: Boolean = false,

/** Custom endpoint to perform AWS API Requests */
val customEndpoint: String? = null,

/** The AWS secret for the access key. */
val secretAccessKey: String? = null
)
12 changes: 11 additions & 1 deletion model/src/main/resources/reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ ort:
key1: value1
key2: value2

aws:
backend:
s3FileStorage:
accessKeyId: "accessKey"
awsRegion: "us-east-1"
bucketName: "ort-scan-results"
compression: false
customEndpoint: "http://localhost:4567"
secretAccessKey: "secret"

clearlyDefined:
serverUrl: 'https://api.clearlydefined.io'

Expand All @@ -298,7 +308,7 @@ ort:
token: token

# Storage readers are listed from highest to lower priority, i.e. the first match wins.
storageReaders: [local, postgres, http, clearlyDefined]
storageReaders: [local, postgres, http, aws, clearlyDefined]

# For storage writers no priority is implied by the order; scan results are stored for all writers.
storageWriters: [postgres]
Expand Down
17 changes: 15 additions & 2 deletions model/src/test/kotlin/config/OrtConfigurationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class OrtConfigurationTest : WordSpec({
enabled shouldBe true

fileStorage shouldNotBeNull {
s3FileStorage should beNull()
httpFileStorage should beNull()
localFileStorage shouldNotBeNull {
directory shouldBe File("~/.ort/scanner/archive")
Expand Down Expand Up @@ -224,6 +225,7 @@ class OrtConfigurationTest : WordSpec({

fileListStorage shouldNotBeNull {
fileStorage shouldNotBeNull {
s3FileStorage should beNull()
httpFileStorage should beNull()
localFileStorage shouldNotBeNull {
directory shouldBe File("~/.ort/scanner/file-lists")
Expand Down Expand Up @@ -291,7 +293,7 @@ class OrtConfigurationTest : WordSpec({

storages shouldNotBeNull {
keys shouldContainExactlyInAnyOrder setOf(
"local", "http", "clearlyDefined", "postgres", "sw360Configuration"
"local", "http", "aws", "clearlyDefined", "postgres", "sw360Configuration"
)

val localStorage = this["local"]
Expand All @@ -309,6 +311,16 @@ class OrtConfigurationTest : WordSpec({
headers should containExactlyEntries("key1" to "value1", "key2" to "value2")
}

val s3Storage = this["aws"]
s3Storage.shouldBeInstanceOf<FileBasedStorageConfiguration>()
s3Storage.backend.s3FileStorage shouldNotBeNull {
bucketName shouldBe "ort-scan-results"
awsRegion shouldBe "us-east-1"
accessKeyId shouldBe "accessKey"
secretAccessKey shouldBe "secret"
compression shouldBe false
}

val cdStorage = this["clearlyDefined"]
cdStorage.shouldBeInstanceOf<ClearlyDefinedStorageConfiguration>()
cdStorage.serverUrl shouldBe "https://api.clearlydefined.io"
Expand Down Expand Up @@ -338,14 +350,15 @@ class OrtConfigurationTest : WordSpec({
sw360Storage.token shouldBe "token"
}

storageReaders shouldContainExactly listOf("local", "postgres", "http", "clearlyDefined")
storageReaders shouldContainExactly listOf("local", "postgres", "http", "aws", "clearlyDefined")
storageWriters shouldContainExactly listOf("postgres")

ignorePatterns shouldContainExactly listOf("**/META-INF/DEPENDENCIES")

provenanceStorage shouldNotBeNull {
fileStorage shouldNotBeNull {
httpFileStorage should beNull()
s3FileStorage should beNull()
localFileStorage shouldNotBeNull {
directory shouldBe File("~/.ort/scanner/provenance")
compression shouldBe false
Expand Down
1 change: 1 addition & 0 deletions model/src/test/kotlin/config/ScannerConfigurationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ScannerConfigurationTest : WordSpec({
actualScannerConfig.storageReaders shouldBe expectedScannerConfig.storageReaders
actualScannerConfig.storageWriters shouldBe expectedScannerConfig.storageWriters
actualScannerConfig.archive?.fileStorage?.httpFileStorage should beNull()
actualScannerConfig.archive?.fileStorage?.s3FileStorage should beNull()

actualStorages.keys shouldContainExactly expectedStorages.keys
actualStorages.entries.forAll { (storageKey, storage) ->
Expand Down
1 change: 1 addition & 0 deletions utils/ort/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {

api(libs.okhttp)

implementation(libs.awsS3)
implementation(libs.commonsCompress)
implementation(libs.kotlinxCoroutines)

Expand Down
Loading

0 comments on commit a5602a2

Please sign in to comment.