Skip to content

API support for Temurin Reproducible Build Attestations #1370

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

Draft
wants to merge 3 commits into
base: main
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
4 changes: 4 additions & 0 deletions adoptium-api-v3-persistence/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.adoptium.api.v3

import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

object XmlMapper {

val mapper: ObjectMapper = XmlMapper(JacksonXmlModule().apply {
setDefaultUseWrapper(false)
}).registerKotlinModule()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package net.adoptium.api.v3.dataSources

import net.adoptium.api.v3.dataSources.models.AdoptRepos
import net.adoptium.api.v3.dataSources.models.AdoptAttestationRepo
import net.adoptium.api.v3.dataSources.persitence.mongo.UpdatedInfo
import net.adoptium.api.v3.models.ReleaseInfo

interface APIDataStore {
fun schedulePeriodicUpdates()
fun getAdoptRepos(): AdoptRepos
fun setAdoptRepos(binaryRepos: AdoptRepos)
fun getAdoptAttestationRepo(): AdoptAttestationRepo
fun setAdoptAttestationRepo(attestationRepo: AdoptAttestationRepo)
fun getReleaseInfo(): ReleaseInfo
fun loadDataFromDb(forceUpdate: Boolean, logEntries: Boolean = true): AdoptRepos
fun loadAttestationDataFromDb(forceUpdate: Boolean, logEntries: Boolean = true): AdoptAttestationRepo
fun getUpdateInfo(): UpdatedInfo
suspend fun isConnectedToDb(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import kotlinx.coroutines.runBlocking
import net.adoptium.api.v3.dataSources.models.AdoptRepos
import net.adoptium.api.v3.dataSources.models.AdoptAttestationRepo
import net.adoptium.api.v3.dataSources.models.FeatureRelease
import net.adoptium.api.v3.dataSources.models.Releases
import net.adoptium.api.v3.dataSources.persitence.ApiPersistence
Expand All @@ -23,6 +24,7 @@ open class APIDataStoreImpl : APIDataStore {
private var dataStore: ApiPersistence
private var updatedAt: UpdatedInfo
private var binaryRepos: AdoptRepos
private var attestationRepo: AdoptAttestationRepo
private var releaseInfo: ReleaseInfo
private var schedule: ScheduledFuture<*>?

Expand Down Expand Up @@ -67,6 +69,32 @@ open class APIDataStoreImpl : APIDataStore {
}
}

fun loadAttestationDataFromDb(
dataStore: ApiPersistence,
previousUpdateInfo: UpdatedInfo,
forceUpdate: Boolean,
previousRepo: AdoptAttestationRepo?,
logEntries: Boolean = true): Pair<AdoptAttestationRepo, UpdatedInfo> {

return runBlocking {
val updated = dataStore.getUpdatedAt()

if (previousRepo == null || forceUpdate || updated != previousUpdateInfo) {
val data = dataStore.readAttestationData()
val updatedAt = dataStore.getUpdatedAt()

val newData = AdoptAttestationRepo(data)

if (logEntries) {
LOGGER.info("Loaded Attestations: $updatedAt")
}
Pair(newData, updatedAt)
} else {
Pair(previousRepo, previousUpdateInfo)
}
}
}

private fun filterValidAssets(data: List<FeatureRelease>): AdoptRepos {
// Ensure that we filter out valid releases/binaries for this ecosystem
val filtered = AdoptRepos(data)
Expand Down Expand Up @@ -131,6 +159,20 @@ open class APIDataStoreImpl : APIDataStore {
AdoptRepos(listOf())
}

attestationRepo = try {
val update = loadAttestationDataFromDb(
dataStore,
updatedAt,
true,
null
)
updatedAt = update.second
update.first
} catch (e: Exception) {
LOGGER.error("Failed to read attestation db", e)
AdoptAttestationRepo(listOf())
}

releaseInfo = loadReleaseInfo()
}

Expand Down Expand Up @@ -194,6 +236,25 @@ open class APIDataStoreImpl : APIDataStore {

}

override fun loadAttestationDataFromDb(
forceUpdate: Boolean,
logEntries: Boolean
): AdoptAttestationRepo {
val update = loadAttestationDataFromDb(
dataStore,
updatedAt,
forceUpdate,
attestationRepo,
logEntries
)

this.updatedAt = update.second
this.attestationRepo = update.first

return attestationRepo

}

override fun getUpdateInfo(): UpdatedInfo {
return updatedAt
}
Expand All @@ -211,6 +272,14 @@ open class APIDataStoreImpl : APIDataStore {
this.binaryRepos = binaryRepos
}

override fun getAdoptAttestationRepo(): AdoptAttestationRepo {
return attestationRepo
}

override fun setAdoptAttestationRepo(attestationRepo: AdoptAttestationRepo) {
this.attestationRepo = attestationRepo
}

private fun periodicUpdate() {
// Must catch errors or may kill the scheduler
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.adoptium.api.v3.dataSources.persitence

import net.adoptium.api.v3.dataSources.models.AdoptRepos
import net.adoptium.api.v3.dataSources.models.AdoptAttestationRepo
import net.adoptium.api.v3.dataSources.models.FeatureRelease
import net.adoptium.api.v3.dataSources.models.GitHubId
import net.adoptium.api.v3.dataSources.models.ReleaseNotes
Expand All @@ -10,11 +11,14 @@ import net.adoptium.api.v3.models.GHReleaseMetadata
import net.adoptium.api.v3.models.GitHubDownloadStatsDbEntry
import net.adoptium.api.v3.models.ReleaseInfo
import net.adoptium.api.v3.models.Vendor
import net.adoptium.api.v3.models.Attestation
import java.time.ZonedDateTime

interface ApiPersistence {
suspend fun updateAllRepos(repos: AdoptRepos, checksum: String)
//suspend fun updateAttestationRepo(repos: AdoptAttestationRepo, checksum: String)
suspend fun readReleaseData(featureVersion: Int): FeatureRelease
suspend fun readAttestationData(): List<Attestation>

suspend fun addGithubDownloadStatsEntries(stats: List<GitHubDownloadStatsDbEntry>)
suspend fun getStatsForFeatureVersion(featureVersion: Int): List<GitHubDownloadStatsDbEntry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.toList
import net.adoptium.api.v3.TimeSource
import net.adoptium.api.v3.dataSources.models.AdoptRepos
import net.adoptium.api.v3.dataSources.models.AdoptAttestationRepo
import net.adoptium.api.v3.dataSources.models.AttestationRepoSummary
import net.adoptium.api.v3.dataSources.models.FeatureRelease
import net.adoptium.api.v3.dataSources.models.GitHubId
import net.adoptium.api.v3.dataSources.models.ReleaseNotes
Expand All @@ -21,6 +23,7 @@ import net.adoptium.api.v3.models.GitHubDownloadStatsDbEntry
import net.adoptium.api.v3.models.Release
import net.adoptium.api.v3.models.ReleaseInfo
import net.adoptium.api.v3.models.Vendor
import net.adoptium.api.v3.models.Attestation
import org.bson.BsonArray
import org.bson.BsonBoolean
import org.bson.BsonDateTime
Expand All @@ -34,6 +37,7 @@ import java.time.ZonedDateTime
open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : MongoInterface(), ApiPersistence {
private val githubReleaseMetadataCollection: MongoCollection<GHReleaseMetadata> = createCollection(mongoClient.getDatabase(), GH_RELEASE_METADATA)
private val releasesCollection: MongoCollection<Release> = createCollection(mongoClient.getDatabase(), RELEASE_DB)
private var attestationsCollection: MongoCollection<Attestation> = createCollection(mongoClient.getDatabase(), ATTESTATIONS_DB)
private val gitHubStatsCollection: MongoCollection<GitHubDownloadStatsDbEntry> = createCollection(mongoClient.getDatabase(), GITHUB_STATS_DB)
private val dockerStatsCollection: MongoCollection<DockerDownloadStatsDbEntry> = createCollection(mongoClient.getDatabase(), DOCKER_STATS_DB)
private val releaseInfoCollection: MongoCollection<ReleaseInfo> = createCollection(mongoClient.getDatabase(), RELEASE_INFO_DB)
Expand All @@ -46,6 +50,7 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M
private val LOGGER = LoggerFactory.getLogger(this::class.java)
const val GH_RELEASE_METADATA = "githubReleaseMetadata"
const val RELEASE_DB = "release"
const val ATTESTATIONS_DB = "attestations"
const val GITHUB_STATS_DB = "githubStats"
const val DOCKER_STATS_DB = "dockerStats"
const val RELEASE_INFO_DB = "releaseInfo"
Expand All @@ -66,6 +71,15 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M
}
}

// override suspend fun updateAttestationRepo(repo: AdoptAttestationRepo, checksum: String) {
//
// try {
// writeAttestations(repo.attestations)
// } finally {
// updateUpdatedTime(TimeSource.now(), checksum, repo.hashCode())
// }
// }

private suspend fun writeReleases(featureVersion: Int, value: FeatureRelease) {
val toAdd = value.releases.getReleases().toList()
if (toAdd.isNotEmpty()) {
Expand All @@ -74,6 +88,16 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M
}
}

private suspend fun writeAttestations(attestations: List<Attestation>) {
if (attestations.isNotEmpty()) {
// Delete all existing
attestationsCollection.drop()
attestationsCollection = createCollection(client.getDatabase(), ATTESTATIONS_DB)

attestationsCollection.insertMany(attestations, InsertManyOptions())
}
}

override suspend fun readReleaseData(featureVersion: Int): FeatureRelease {
val releases = releasesCollection
.find(majorVersionMatcher(featureVersion))
Expand All @@ -82,6 +106,12 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M
return FeatureRelease(featureVersion, Releases(releases))
}

override suspend fun readAttestationData(): List<Attestation> {
return attestationsCollection
.find(Document())
.toList()
}

override suspend fun addGithubDownloadStatsEntries(stats: List<GitHubDownloadStatsDbEntry>) {
gitHubStatsCollection.insertMany(stats)
}
Expand Down Expand Up @@ -182,6 +212,7 @@ open class MongoApiPersistence @Inject constructor(mongoClient: MongoClient) : M
}

private fun majorVersionMatcher(featureVersion: Int) = Document("version_data.major", featureVersion)
private fun attestationMajorVersionMatcher(featureVersion: Int) = Document("featureVersion", featureVersion)

override suspend fun getGhReleaseMetadata(gitHubId: GitHubId): GHReleaseMetadata? {
return githubReleaseMetadataCollection.find(matchGithubId(gitHubId)).firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.adoptium.api.v3.dataSources.models

import net.adoptium.api.v3.models.Attestation

class AdoptAttestationRepo(val attestations: List<Attestation>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package net.adoptium.api.v3.dataSources.models

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

/* Format example:
Query:
query RepoFiles($owner: String!, $name: String!, $expr: String!) {
repository(owner: $owner, name: $name) {
object(expression: $expr) {
... on Tree {
entries {
name
type
object {
... on Tree {
entries {
name
type
object {
... on Blob {
commitResourcePath
}
}
}
}
}
}
}
}
}
}
Response:
{
"data": {
"repository": {
"object": {
"entries": [
{
"name": ".github",
"type": "tree",
"object": {
"entries": [
{
"name": "workflows",
"type": "tree",
"object": {}
}
]
}
},
{
"name": "21",
"type": "tree",
"object": {
"entries": [
{
"name": "jdk_21_0_5_11_x64_linux_Adoptium.xml",
"type": "blob",
"object": {
"commitResourcePath": "/andrew-m-leonard/temurin-attestations/commit/97d84d270a34f66749e5e66b0a42996828bda4e1"
}
},
{
"name": "jdk_21_0_5_11_x64_linux_Adoptium.xml.sign.pub",
"type": "blob",
"object": {
"commitResourcePath": "/andrew-m-leonard/temurin-attestations/commit/d65966533a06d90147e8f4ad963dcf7bb52e645e"
}
}
]
}
},
{
"name": "README.md",
"type": "blob",
"object": {}
}
]
}
}
}
}

*/

data class AttestationRepoSummary @JsonCreator constructor(
@JsonProperty("data") val data: AttestationRepoSummaryData?
) {
}

data class AttestationRepoSummaryData @JsonCreator constructor(
@JsonProperty("repository") val repository: AttestationRepoSummaryRepository?
) {
}

data class AttestationRepoSummaryRepository @JsonCreator constructor(
@JsonProperty("object") val att_object: AttestationRepoSummaryObject?
) {
}

data class AttestationRepoSummaryObject @JsonCreator constructor(
@JsonProperty("commitResourcePath") val commitResourcePath: String?,
@JsonProperty("entries") val entries: List<AttestationRepoSummaryEntry>?
) {
}

data class AttestationRepoSummaryEntry @JsonCreator constructor(
@JsonProperty("name") val name: String,
@JsonProperty("type") val type: String,
@JsonProperty("object") val att_object: AttestationRepoSummaryObject?
) {
}

Loading
Loading