Skip to content

Commit 1704c0f

Browse files
committed
feat(indexing): schedule indexing based on cron trigger
1 parent ec26f23 commit 1704c0f

11 files changed

Lines changed: 149 additions & 6 deletions

File tree

ambassador-application/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ plugins {
1212
val springdocVersion: String by extra
1313
val kotlinCoroutinesVersion: String by extra
1414
val springApiVersion: String by extra
15+
val shedlockVersion: String by extra
1516

1617
dependencies {
1718
implementation(project(":ambassador-model"))
@@ -32,6 +33,9 @@ dependencies {
3233
implementation("com.github.ben-manes.caffeine:caffeine")
3334
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
3435

36+
implementation("net.javacrumbs.shedlock:shedlock-spring:$shedlockVersion")
37+
implementation("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:$shedlockVersion")
38+
3539
implementation("io.github.filipowm:spring-api-starter:$springApiVersion")
3640

3741
implementation("org.springdoc:springdoc-openapi-webflux-ui:$springdocVersion")

ambassador-application/src/main/kotlin/com/roche/ambassador/configuration/properties/IndexerProperties.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ data class IndexerProperties(
3737

3838
@NestedConfigurationProperty
3939
@Valid
40-
val subscription: SubscriptionProperties = SubscriptionProperties()
40+
val subscription: SubscriptionProperties = SubscriptionProperties(),
41+
42+
@NestedConfigurationProperty
43+
@Valid
44+
val scheduler: SchedulerProperties = SchedulerProperties(),
4145

4246
)
4347

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.roche.ambassador.configuration.properties
2+
3+
import java.time.Duration
4+
5+
data class SchedulerProperties(
6+
val enabled: Boolean = false,
7+
val cron: String = "0 0 14 ? * SUN",
8+
val lockFor: Duration = Duration.ofMinutes(30)
9+
)

ambassador-application/src/main/kotlin/com/roche/ambassador/indexing/IndexingConfiguration.kt

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ import com.roche.ambassador.configuration.properties.IndexingLockType
55
import com.roche.ambassador.extensions.LoggerDelegate
66
import com.roche.ambassador.extensions.toPrettyString
77
import com.roche.ambassador.storage.indexing.IndexingRepository
8+
import net.javacrumbs.shedlock.core.LockProvider
9+
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider
10+
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock
11+
import org.springframework.beans.factory.InitializingBean
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
813
import org.springframework.context.annotation.Bean
914
import org.springframework.context.annotation.Configuration
15+
import org.springframework.jdbc.core.JdbcTemplate
16+
import org.springframework.scheduling.annotation.EnableScheduling
17+
import javax.sql.DataSource
1018

1119
@Configuration
12-
internal class IndexingConfiguration(
13-
private val indexerProperties: IndexerProperties
14-
) {
20+
internal class IndexingConfiguration(private val indexerProperties: IndexerProperties) {
1521

16-
private val log by LoggerDelegate()
22+
companion object {
23+
private val log by LoggerDelegate()
24+
}
1725

1826
@Bean
1927
fun indexingLock(indexingRepository: IndexingRepository): IndexingLock {
@@ -23,4 +31,31 @@ internal class IndexingConfiguration(
2331
IndexingLockType.DATABASE -> IndexingLock.createDatabaseLock(indexingRepository)
2432
}
2533
}
34+
35+
@Configuration
36+
@ConditionalOnProperty(prefix = "ambassador.indexer.scheduler", name = ["enabled"], matchIfMissing = false, havingValue = "true")
37+
@EnableScheduling
38+
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
39+
inner class SchedulerConfiguration : InitializingBean {
40+
41+
override fun afterPropertiesSet() {
42+
log.info("Initialized indexing scheduler with cron: ${indexerProperties.scheduler.cron}")
43+
}
44+
45+
@Bean
46+
fun lockProvider(dataSource: DataSource): LockProvider {
47+
return JdbcTemplateLockProvider(
48+
JdbcTemplateLockProvider.Configuration.builder()
49+
.withJdbcTemplate(JdbcTemplate(dataSource))
50+
.usingDbTime()
51+
.build()
52+
)
53+
}
54+
55+
@Bean
56+
fun indexingScheduler(indexingService: IndexingService): ScheduledIndexingInitializer {
57+
return ScheduledIndexingInitializer(indexingService)
58+
}
59+
}
60+
2661
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.roche.ambassador.indexing
2+
3+
import com.roche.ambassador.extensions.LoggerDelegate
4+
import com.roche.ambassador.security.RunAsTechnicalUser
5+
import kotlinx.coroutines.runBlocking
6+
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock
7+
import org.springframework.scheduling.annotation.Scheduled
8+
9+
internal open class ScheduledIndexingInitializer(private val indexingService: IndexingService) {
10+
11+
companion object {
12+
private val log by LoggerDelegate()
13+
}
14+
15+
@Scheduled(cron = "\${ambassador.indexer.scheduler.cron}")
16+
@SchedulerLock(name = " indexing", lockAtLeastFor = "\${ambassador.indexer.scheduler.lockFor}", lockAtMostFor = "1h")
17+
@RunAsTechnicalUser
18+
open fun triggerScheduling() {
19+
log.info("Triggering scheduled indexing")
20+
// purposely run blocking, cause it will get async in downstream when indexing is triggered successfully
21+
runBlocking {
22+
try {
23+
indexingService.reindex()
24+
} catch (ex: IndexingAlreadyStartedException) {
25+
log.warn("Unable to start scheduled indexing because it is already running")
26+
}
27+
}
28+
}
29+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.roche.ambassador.security
2+
3+
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
annotation class RunAsTechnicalUser
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.roche.ambassador.security
2+
3+
import org.aspectj.lang.ProceedingJoinPoint
4+
import org.aspectj.lang.annotation.Around
5+
import org.aspectj.lang.annotation.Aspect
6+
import org.springframework.security.authentication.AbstractAuthenticationToken
7+
import org.springframework.security.core.context.ReactiveSecurityContextHolder
8+
import org.springframework.stereotype.Component
9+
10+
@Aspect
11+
@Component
12+
internal open class RunAsTechnicalUserAspect {
13+
14+
companion object {
15+
val technicalUser = AmbassadorUser(
16+
"Technical User",
17+
"_admin", "admin@admin.com",
18+
mapOf(), listOf("ROLE_ADMIN", "ROLE_USER")
19+
)
20+
private val technicalUserToken = TechnicalUserToken(technicalUser)
21+
}
22+
23+
private class TechnicalUserToken(private val ambassadorUser: AmbassadorUser) : AbstractAuthenticationToken(ambassadorUser.authorities) {
24+
override fun getCredentials(): Any = "__none__"
25+
26+
override fun getPrincipal(): AmbassadorUser = ambassadorUser
27+
28+
}
29+
30+
@Around("@annotation(com.roche.ambassador.security.RunAsTechnicalUser)")
31+
open fun logExecutionTime(joinPoint: ProceedingJoinPoint): Any? {
32+
val savedContext = ReactiveSecurityContextHolder.getContext()
33+
ReactiveSecurityContextHolder.withAuthentication(technicalUserToken)
34+
try {
35+
return joinPoint.proceed()
36+
} finally {
37+
if (savedContext != null) {
38+
ReactiveSecurityContextHolder.withSecurityContext(savedContext)
39+
} else {
40+
ReactiveSecurityContextHolder.clearContext()
41+
}
42+
}
43+
}
44+
}

ambassador-application/src/main/resources/application.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ ambassador:
7777
security:
7878
enabled: true
7979
indexer:
80+
scheduler:
81+
enabled: true
82+
cron: "0 0 14 ? * SUN"
83+
lockFor: 30m
8084
features:
8185
requireVisibility:
8286
- public

ambassador-gitlab-client/src/main/kotlin/com/roche/gitlab/api/project/model/FeatureAccessLevel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package com.roche.gitlab.api.project.model
22

33
enum class FeatureAccessLevel {
44

5+
PUBLIC,
56
DISABLED,
67
PRIVATE,
78
ENABLED
89
;
910

10-
fun canEveryoneAccess(): Boolean = this == ENABLED
11+
fun canEveryoneAccess(): Boolean = this == ENABLED || this == PUBLIC
1112
fun canNooneAccess(): Boolean = this == DISABLED
1213
fun canOnlyProjectMembersAccess(): Boolean = this == PRIVATE
1314
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE shedlock(
2+
name VARCHAR(64) NOT NULL,
3+
lock_until TIMESTAMP NOT NULL,
4+
locked_at TIMESTAMP NOT NULL,
5+
locked_by VARCHAR(255) NOT NULL,
6+
PRIMARY KEY (name)
7+
);

0 commit comments

Comments
 (0)