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 endpoint for initializing risc and sops config by calling init-ri… #286

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions src/main/kotlin/no/risc/config/InitRiScServiceConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package no.risc.config

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties(prefix = "init-risc")
class InitRiScServiceConfig {
lateinit var baseUrl: String
}
13 changes: 13 additions & 0 deletions src/main/kotlin/no/risc/exception/GlobalExceptionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package no.risc.exception
import no.risc.exception.exceptions.AccessTokenValidationFailedException
import no.risc.exception.exceptions.CreatePullRequestException
import no.risc.exception.exceptions.CreatingRiScException
import no.risc.exception.exceptions.GenerateInitialRiScException
import no.risc.exception.exceptions.InvalidAccessTokensException
import no.risc.exception.exceptions.JSONSchemaFetchException
import no.risc.exception.exceptions.PermissionDeniedOnGitHubException
Expand Down Expand Up @@ -156,6 +157,18 @@ internal class GlobalExceptionHandler {
)
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
@ExceptionHandler(GenerateInitialRiScException::class)
fun handleGenerateInitialRiScException(ex: GenerateInitialRiScException): ProcessRiScResultDTO {
logger.error(ex.message, ex)
return ProcessRiScResultDTO(
ex.riScId,
ProcessingStatus.ErrorWhenGeneratingInitialRiSc,
ex.message,
)
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
@ExceptionHandler(UnableToParseResponseBodyException::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package no.risc.exception.exceptions

data class GenerateInitialRiScException(
override val message: String,
val riScId: String,
) : Exception()
82 changes: 80 additions & 2 deletions src/main/kotlin/no/risc/github/GithubConnector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import no.risc.exception.exceptions.PermissionDeniedOnGitHubException
import no.risc.exception.exceptions.SopsConfigFetchException
import no.risc.github.models.FileContentDTO
import no.risc.github.models.FileNameDTO
import no.risc.github.models.PutFileContentsDTO
import no.risc.github.models.RepositoryDTO
import no.risc.github.models.ShaResponseDTO
import no.risc.infra.connector.WebClientConnector
Expand Down Expand Up @@ -287,11 +288,12 @@ class GithubConnector(
requiresNewApproval: Boolean,
accessTokens: AccessTokens,
userInfo: UserInfo,
commitSha: String?,
): RiScApprovalPRStatus {
val accessToken = accessTokens.githubAccessToken.value
val githubAuthor = Author(userInfo.name, userInfo.email, Date.from(Instant.now()))
// Attempt to get SHA for the existing draft
var latestShaForDraft = getSHAForExistingRiScDraftOrNull(owner, repository, riScId, accessToken)
var latestShaForDraft = commitSha ?: getSHAForExistingRiScDraftOrNull(owner, repository, riScId, accessToken)
var latestShaForPublished: String? = ""

// Determine if a new branch is needed. "requires new approval" is used to determine if new PR can be created
Expand All @@ -310,7 +312,11 @@ class GithubConnector(
"Create new RiSc with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
}
} else {
"Update RiSc with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
if (commitSha != null) {
"Initialize generated RiSc with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
} else {
"Update RiSc with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
}
}

putFileRequestToGithub(
Expand Down Expand Up @@ -393,6 +399,18 @@ class GithubConnector(
}
}

private fun getSHAForExistingSopsConfigDraftOrNull(
owner: String,
repository: String,
riScId: String,
accessToken: String,
) = try {
getGithubResponse(githubHelper.uriToFindSopsConfigOnDraftBranch(owner, repository, riScId), accessToken)
.shaResponseDTO()
} catch (e: Exception) {
null
}

private fun getSHAForExistingRiScDraftOrNull(
owner: String,
repository: String,
Expand All @@ -405,6 +423,18 @@ class GithubConnector(
null
}

private fun getSHAForPublishedSopsConfigOrNull(
owner: String,
repository: String,
riScId: String,
accessToken: String,
) = try {
getGithubResponse(githubHelper.uriToFindSopsConfig(owner, repository, riScId), accessToken)
.shaResponseDTO()
} catch (e: Exception) {
null
}

private fun getSHAForPublishedRiScOrNull(
owner: String,
repository: String,
Expand Down Expand Up @@ -576,6 +606,54 @@ class GithubConnector(
.body(Mono.just(writePayload.toContentBody()), String::class.java)
.retrieve()

fun writeSopsConfig(
sopsConfig: String,
repositoryOwner: String,
repositoryName: String,
riScId: String,
accessTokens: AccessTokens,
userInfo: UserInfo,
defaultBranch: String,
requiresNewApproval: Boolean,
): PutFileContentsDTO {
val accessToken = accessTokens.githubAccessToken.value
val githubAuthor = Author(userInfo.name, userInfo.email, Date.from(Instant.now()))
// Attempt to get SHA for the existing draft
var latestShaForDraft = getSHAForExistingSopsConfigDraftOrNull(repositoryOwner, repositoryName, riScId, accessToken)
var latestShaForPublished: String? = ""

// Determine if a new branch is needed. "requires new approval" is used to determine if new PR can be created
// through updating.
val commitMessage =
if (latestShaForDraft == null) {
createNewBranch(repositoryOwner, repositoryName, riScId, accessToken, defaultBranch)
// Fetch again after creating branch as it will return null if no branch exists
latestShaForDraft = getSHAForExistingSopsConfigDraftOrNull(repositoryOwner, repositoryName, riScId, accessToken)

// Fetch to determine if update or create
latestShaForPublished = getSHAForPublishedSopsConfigOrNull(repositoryOwner, repositoryName, riScId, accessToken)
if (latestShaForPublished != null) {
"Update SOPS config with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
} else {
"Create new SOPS config with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
}
} else {
"Update SOPS config with id: $riScId" + if (requiresNewApproval) " requires new approval" else ""
}
return putFileRequestToGithub(
githubHelper.uriToPutSopsConfigOnDraftBranch(repositoryOwner, repositoryName, riScId),
accessToken,
GithubWriteToFilePayload(
message = commitMessage,
content = sopsConfig.encodeBase64(),
sha = latestShaForDraft,
branchName = riScId,
author = githubAuthor,
),
).bodyToMono<PutFileContentsDTO>().block()
?: throw IllegalStateException("Failed to de-serialize response from writing SOPS config to GitHub")
}

private suspend fun getGithubResponseSuspend(
uri: String,
accessToken: String,
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/no/risc/github/GithubHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ class GithubHelper(
repository: String,
): String = "/$owner/$repository/contents/$riScFolderPath"

fun uriToFindSopsConfig(
owner: String,
repository: String,
id: String,
): String = "/$owner/$repository/contents/$riScFolderPath/.sops.yaml"

fun uriToFindRiSc(
owner: String,
repository: String,
Expand Down Expand Up @@ -138,13 +144,27 @@ class GithubHelper(
draftBranch: String = riScId,
): String = "/$owner/$repository/contents/$riScFolderPath/$riScId.$filenamePostfix.yaml?ref=$draftBranch"

fun uriToFindSopsConfigOnDraftBranch(
owner: String,
repository: String,
riScId: String,
draftBranch: String = riScId,
): String = "/$owner/$repository/contents/$riScFolderPath/.sops.yaml?ref=$draftBranch"

fun uriToPutRiScOnDraftBranch(
owner: String,
repository: String,
riScId: String,
draftBranch: String = riScId,
): String = "/$owner/$repository/contents/$riScFolderPath/$riScId.$filenamePostfix.yaml?ref=$draftBranch"

fun uriToPutSopsConfigOnDraftBranch(
owner: String,
repository: String,
riScId: String,
draftBranch: String = riScId,
): String = "/$owner/$repository/contents/$riScFolderPath/.sops.yaml?ref=$draftBranch"

fun uriToGetCommitStatus(
owner: String,
repository: String,
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/no/risc/github/models/GithubResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ data class RepositoryPermissions(
val triage: Boolean,
val pull: Boolean,
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class PutFileContentsDTO(
val commit: GitCommit,
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class GitCommit(
val sha: String,
)
6 changes: 6 additions & 0 deletions src/main/kotlin/no/risc/github/models/SopsConfigOnGitHub.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package no.risc.github.models

data class SopsConfigOnGitHub(
val config: String,
val commitSha: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package no.risc.infra.connector

import no.risc.config.InitRiScServiceConfig
import org.springframework.stereotype.Component

@Component
class InitRiScServiceConnector(
val initRiScServiceConfig: InitRiScServiceConfig,
) : WebClientConnector(baseURL = initRiScServiceConfig.baseUrl)
30 changes: 30 additions & 0 deletions src/main/kotlin/no/risc/initRiSc/InitRiScServiceIntegration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package no.risc.initRiSc

import no.risc.infra.connector.InitRiScServiceConnector
import no.risc.initRiSc.model.GenerateRiScRequestBody
import no.risc.initRiSc.model.GenerateRiScResponseBody
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.bodyToMono

@Component
class InitRiScServiceIntegration(
val initRiScServiceConnector: InitRiScServiceConnector,
) {
fun getInitialRiScAndSopsConfig(
repositoryName: String,
publicAgeKey: String?,
gcpProjectId: String,
): GenerateRiScResponseBody? {
val r =
initRiScServiceConnector.webClient
.post()
.uri("/generate/$repositoryName")
.body(BodyInserters.fromValue(GenerateRiScRequestBody(publicAgeKey, gcpProjectId)))
.retrieve()
.bodyToMono<GenerateRiScResponseBody>()

return r
.block()
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/no/risc/initRiSc/model/DTOs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.risc.initRiSc.model

import no.risc.risc.models.UserInfo

data class GenerateRiScResponseBody(
val sopsConfig: String,
val schemaVersion: String,
val initialRiScContent: String,
val userInfo: UserInfo,
)

data class GenerateRiScRequestBody(
val publicAgeKey: String? = null,
val gcpProjectId: String,
)
21 changes: 21 additions & 0 deletions src/main/kotlin/no/risc/risc/RiScController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import no.risc.infra.connector.models.GCPAccessToken
import no.risc.infra.connector.models.GithubAccessToken
import no.risc.risc.models.DifferenceDTO
import no.risc.risc.models.DifferenceRequestBody
import no.risc.risc.models.GenerateInitialRiScRequestBody
import no.risc.risc.models.RiScWrapperObject
import no.risc.risc.models.UserInfo
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -127,6 +128,26 @@ class RiScController(
}
}

@PostMapping("/{repositoryOwner}/{repositoryName}/initialize")
suspend fun generateInitialRiSc(
@RequestHeader("GCP-Access-Token") gcpAccessToken: String,
@RequestHeader("GitHub-Access-Token") gitHubAccessToken: String,
@PathVariable repositoryOwner: String,
@PathVariable repositoryName: String,
@RequestBody generateInitialRiScRequestBody: GenerateInitialRiScRequestBody,
): RiScContentResultDTO =
riScService.initializeGeneratedRiSc(
repositoryOwner,
repositoryName,
generateInitialRiScRequestBody.publicAgeKey,
generateInitialRiScRequestBody.gcpProjectId,
AccessTokens(
gcpAccessToken = GCPAccessToken(gcpAccessToken),
githubAccessToken = GithubAccessToken(gitHubAccessToken),
),
defaultBranch = githubConnector.fetchDefaultBranch(repositoryOwner, repositoryName, gitHubAccessToken),
)

@PostMapping("/{repositoryOwner}/{repositoryName}/{riscId}/difference", produces = ["application/json"])
suspend fun getDifferenceBetweenTwoRiScs(
@RequestHeader("GCP-Access-Token") gcpAccessToken: String,
Expand Down
Loading
Loading