Skip to content

Commit

Permalink
Fix merge conflicts from main
Browse files Browse the repository at this point in the history
  • Loading branch information
geoandri committed Dec 12, 2024
2 parents e1cf41f + e39db2f commit dd1ceb5
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package net.leanix.githubagent.controllers

import net.leanix.githubagent.services.GitHubWebhookHandler
import net.leanix.githubagent.services.GitHubWebhookService
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand All @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("github")
class GitHubWebhookController(private val gitHubWebhookHandler: GitHubWebhookHandler) {
class GitHubWebhookController(private val gitHubWebhookService: GitHubWebhookService) {

@PostMapping("/webhook")
@ResponseStatus(HttpStatus.ACCEPTED)
Expand All @@ -21,6 +21,6 @@ class GitHubWebhookController(private val gitHubWebhookHandler: GitHubWebhookHan
@RequestHeader("X-Hub-Signature-256", required = false) signature256: String?,
@RequestBody payload: String
) {
gitHubWebhookHandler.handleWebhookEvent(eventType, host, signature256, payload)
gitHubWebhookService.handleWebhookEvent(eventType, host, signature256, payload)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.leanix.githubagent.dto

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
data class InstallationEventPayload(
val action: String,
val installation: InstallationEventInstallation,
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class InstallationEventInstallation(
val id: Int,
val account: InstallationEventInstallationAccount
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class InstallationEventInstallationAccount(
val login: String,
val id: Int
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package net.leanix.githubagent.runners

import net.leanix.githubagent.dto.LogLevel
import net.leanix.githubagent.dto.SynchronizationProgress
import net.leanix.githubagent.handler.BrokerStompSessionHandler
import net.leanix.githubagent.services.CachingService
import net.leanix.githubagent.services.GitHubAuthenticationService
Expand All @@ -15,7 +13,6 @@ import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.util.*

@Component
@Profile("!test")
Expand All @@ -38,25 +35,15 @@ class PostStartupRunner(
return
}
kotlin.runCatching {
startFullScan()
syncLogService.sendFullScanStart(null)
scanResources()
}.onSuccess {
fullScanSuccess()
syncLogService.sendFullScanSuccess()
}.onFailure {
fullScanFailure(it.message)
syncLogService.sendFullScanFailure(it.message)
}
}

private fun startFullScan() {
cachingService.set("runId", UUID.randomUUID(), null)
logger.info("Starting full sync")
syncLogService.sendSyncLog(
logLevel = LogLevel.INFO,
synchronizationProgress = SynchronizationProgress.PENDING,
message = "Starting synchronization"
)
}

private fun scanResources() {
githubAuthenticationService.generateAndCacheJwtToken()
val jwt = cachingService.get("jwtToken") as String
Expand All @@ -66,26 +53,4 @@ class PostStartupRunner(
)
gitHubScanningService.scanGitHubResources()
}

private fun fullScanSuccess() {
syncLogService.sendSyncLog(
logLevel = LogLevel.INFO,
synchronizationProgress = SynchronizationProgress.FINISHED,
message = "Synchronization finished."
)
cachingService.remove("runId")
logger.info("Full sync finished")
}

private fun fullScanFailure(errorMessage: String?) {
val message = "Synchronization aborted. " +
"An error occurred while scanning GitHub resources. Error: $errorMessage"
syncLogService.sendSyncLog(
logLevel = LogLevel.ERROR,
synchronizationProgress = SynchronizationProgress.ABORTED,
message = message
)
cachingService.remove("runId")
logger.error(message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class GitHubScanningService(
webSocketService.sendMessage("${cachingService.get("runId")}/organizations", organizations)
}

private fun fetchAndSendRepositoriesData(installation: Installation): List<RepositoryDto> {
fun fetchAndSendRepositoriesData(installation: Installation): List<RepositoryDto> {
val installationToken = cachingService.get("installationToken:${installation.id}").toString()
var cursor: String? = null
var totalRepos = 0
Expand All @@ -97,7 +97,7 @@ class GitHubScanningService(
return repositories
}

private fun fetchManifestFilesAndSend(installation: Installation, repository: RepositoryDto) {
fun fetchManifestFilesAndSend(installation: Installation, repository: RepositoryDto) {
val manifestFiles = fetchManifestFiles(installation, repository.name).getOrThrow().items
val manifestFilesContents = fetchManifestContents(
installation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import net.leanix.githubagent.shared.SUPPORTED_EVENT_TYPES
import net.leanix.githubagent.shared.hmacSHA256
import net.leanix.githubagent.shared.timingSafeEqual
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service

@Service
class GitHubWebhookHandler(
class GitHubWebhookService(
private val webhookEventService: WebhookEventService,
private val gitHubEnterpriseProperties: GitHubEnterpriseProperties
) {

private val logger = LoggerFactory.getLogger(GitHubWebhookHandler::class.java)
private val logger = LoggerFactory.getLogger(GitHubWebhookService::class.java)

@Async
fun handleWebhookEvent(eventType: String, host: String, signature256: String?, payload: String) {
if (SUPPORTED_EVENT_TYPES.contains(eventType.uppercase())) {
if (!gitHubEnterpriseProperties.baseUrl.contains(host)) {
Expand All @@ -38,6 +40,7 @@ class GitHubWebhookHandler(
} else {
logger.warn("Webhook secret is not set, Skipping signature verification")
}
logger.info("Received a webhook event of type: $eventType")
webhookEventService.consumeWebhookEvent(eventType, payload)
} else {
logger.warn("Received an unsupported event of type: $eventType")
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/net/leanix/githubagent/services/SyncLogService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import net.leanix.githubagent.dto.SyncLogDto
import net.leanix.githubagent.dto.SynchronizationProgress
import net.leanix.githubagent.dto.Trigger
import net.leanix.githubagent.shared.LOGS_TOPIC
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.util.UUID

Expand All @@ -13,6 +14,45 @@ class SyncLogService(
private val webSocketService: WebSocketService,
private val cachingService: CachingService
) {
private val logger = LoggerFactory.getLogger(SyncLogService::class.java)

fun sendFullScanStart(orgName: String?) {
cachingService.set("runId", UUID.randomUUID(), null)
val message = if (orgName != null) {
"Starting synchronization for organization $orgName"
} else {
"Starting synchronization"
}
logger.info(message)
sendSyncLog(
logLevel = LogLevel.INFO,
synchronizationProgress = SynchronizationProgress.PENDING,
message = message
)
}

fun sendFullScanSuccess() {
sendSyncLog(
logLevel = LogLevel.INFO,
synchronizationProgress = SynchronizationProgress.FINISHED,
message = "Synchronization finished."
)
cachingService.remove("runId")
logger.info("Full sync finished")
}

fun sendFullScanFailure(errorMessage: String?) {
val message = "Synchronization aborted. " +
"An error occurred while scanning GitHub resources. Error: $errorMessage"
sendSyncLog(
logLevel = LogLevel.ERROR,
synchronizationProgress = SynchronizationProgress.ABORTED,
message = message
)
cachingService.remove("runId")
logger.error(message)
}

fun sendErrorLog(message: String) {
sendSyncLog(message, LOGS_TOPIC, LogLevel.ERROR, SynchronizationProgress.RUNNING)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package net.leanix.githubagent.services

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import net.leanix.githubagent.dto.Account
import net.leanix.githubagent.dto.Installation
import net.leanix.githubagent.dto.InstallationEventPayload
import net.leanix.githubagent.dto.ManifestFileAction
import net.leanix.githubagent.dto.ManifestFileUpdateDto
import net.leanix.githubagent.dto.PushEventCommit
Expand All @@ -10,14 +13,18 @@ import net.leanix.githubagent.shared.MANIFEST_FILE_NAME
import net.leanix.githubagent.shared.fileNameMatchRegex
import net.leanix.githubagent.shared.generateFullPath
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service

@Service
class WebhookEventService(
private val webSocketService: WebSocketService,
private val gitHubGraphQLService: GitHubGraphQLService,
private val cachingService: CachingService,
private val gitHubAuthenticationService: GitHubAuthenticationService
private val gitHubAuthenticationService: GitHubAuthenticationService,
private val gitHubScanningService: GitHubScanningService,
private val syncLogService: SyncLogService,
@Value("\${webhookEventService.waitingTime}") private val waitingTime: Long
) {

private val logger = LoggerFactory.getLogger(WebhookEventService::class.java)
Expand All @@ -26,6 +33,7 @@ class WebhookEventService(
fun consumeWebhookEvent(eventType: String, payload: String) {
when (eventType.uppercase()) {
"PUSH" -> handlePushEvent(payload)
"INSTALLATION" -> handleInstallationEvent(payload)
else -> {
logger.info("Sending event of type: $eventType")
webSocketService.sendMessage("/events/other", payload)
Expand Down Expand Up @@ -55,6 +63,35 @@ class WebhookEventService(
}
}

private fun handleInstallationEvent(payload: String) {
val installationEventPayload: InstallationEventPayload = objectMapper.readValue(payload)
if (installationEventPayload.action == "created") {
handleInstallationCreated(installationEventPayload)
}
}

private fun handleInstallationCreated(installationEventPayload: InstallationEventPayload) {
while (cachingService.get("runId") != null) {
logger.info("A full scan is already in progress, waiting for it to finish.")
Thread.sleep(waitingTime)
}
syncLogService.sendFullScanStart(installationEventPayload.installation.account.login)
kotlin.runCatching {
val installation = Installation(
installationEventPayload.installation.id.toLong(),
Account(installationEventPayload.installation.account.login)
)
gitHubAuthenticationService.refreshTokens()
gitHubScanningService.fetchAndSendRepositoriesData(installation).forEach { repository ->
gitHubScanningService.fetchManifestFilesAndSend(installation, repository)
}
}.onSuccess {
syncLogService.sendFullScanSuccess()
}.onFailure {
syncLogService.sendFullScanFailure(it.message)
}
}

private fun handleManifestFileChanges(
defaultBranch: String,
headCommit: PushEventCommit,
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ leanix:
auth:
access-token-uri: ${leanix.base-url}/mtm/v1
technical-user-token: ${LEANIX_TECHNICAL_USER_TOKEN}
webhookEventService:
waitingTime: 10000

resilience4j.retry:
configs:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package net.leanix.githubagent.controllers

import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
import io.mockk.every
import io.mockk.verify
import net.leanix.githubagent.exceptions.WebhookSecretNotSetException
import net.leanix.githubagent.services.CachingService
import net.leanix.githubagent.services.GitHubWebhookHandler
import net.leanix.githubagent.services.GitHubWebhookService
import net.leanix.githubagent.services.SyncLogService
import net.leanix.githubagent.services.WebhookEventService
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -21,7 +24,10 @@ class GitHubWebhookControllerTest {
private lateinit var mockMvc: MockMvc

@MockkBean
private lateinit var gitHubWebhookHandler: GitHubWebhookHandler
private lateinit var webhookEventService: WebhookEventService

@SpykBean
private lateinit var gitHubWebhookService: GitHubWebhookService

@MockkBean
private lateinit var syncLogService: SyncLogService
Expand All @@ -42,7 +48,8 @@ class GitHubWebhookControllerTest {
val payload = "{}"
val host = "valid.host"

every { gitHubWebhookHandler.handleWebhookEvent(any(), any(), any(), any()) } returns Unit
every { gitHubWebhookService.handleWebhookEvent(any(), any(), any(), any()) } returns Unit
every { webhookEventService.consumeWebhookEvent(any(), any()) } returns Unit

mockMvc.perform(
MockMvcRequestBuilders.post("/github/webhook")
Expand All @@ -61,7 +68,7 @@ class GitHubWebhookControllerTest {
val signature256 = "sha256=invalidsignature"

every {
gitHubWebhookHandler.handleWebhookEvent(
gitHubWebhookService.handleWebhookEvent(
eventType, host, signature256, payload
)
} throws WebhookSecretNotSetException()
Expand All @@ -75,4 +82,32 @@ class GitHubWebhookControllerTest {
)
.andExpect(MockMvcResultMatchers.status().isBadRequest)
}

@Test
fun `should process installation created event successfully`() {
val eventType = "INSTALLATION"
val payload = """{
"action": "created",
"installation": {
"id": 30,
"account": {
"login": "test-org",
"id": 20
}
}
}"""
val host = "dummy"

every { webhookEventService.consumeWebhookEvent(any(), any()) } returns Unit

mockMvc.perform(
MockMvcRequestBuilders.post("/github/webhook")
.header("X-GitHub-Event", eventType)
.header("X-GitHub-Enterprise-Host", host)
.content(payload)
)
.andExpect(MockMvcResultMatchers.status().isAccepted)

verify(exactly = 1) { webhookEventService.consumeWebhookEvent(any(), any()) }
}
}
Loading

0 comments on commit dd1ceb5

Please sign in to comment.