Skip to content

Commit

Permalink
feat: add cloud storage backup cronjob (#350)
Browse files Browse the repository at this point in the history
  • Loading branch information
kosmoz committed Nov 8, 2023
1 parent 9793c85 commit 2b448dc
Show file tree
Hide file tree
Showing 27 changed files with 445 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion deploy/crd/planes.glasskube.eu-v1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,14 @@ spec:
type: object
region:
type: string
endpoint:
hostname:
nullable: true
type: string
port:
nullable: true
type: integer
useSsl:
type: boolean
usePathStyle:
nullable: true
type: boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.glasskube.operator.apps.common.backup

import com.fasterxml.jackson.annotation.JsonIgnore
import eu.glasskube.operator.apps.common.cloudstorage.CloudStorageSpec
import io.fabric8.generator.annotation.Nullable
import io.fabric8.generator.annotation.Required
import io.fabric8.kubernetes.api.model.SecretKeySelector
Expand All @@ -13,29 +13,18 @@ data class BackupSpec(
) {
data class S3Spec(
@field:Nullable
val hostname: String?,
override val hostname: String?,
@field:Nullable
val port: Int?,
val useSsl: Boolean = true,
override val port: Int?,
override val useSsl: Boolean = true,
@field:Nullable
val region: String?,
override val region: String?,
@field:Required
val bucket: String,
override val bucket: String,
@field:Required
val accessKeySecret: SecretKeySelector,
override val accessKeySecret: SecretKeySelector,
@field:Required
val secretKeySecret: SecretKeySelector,
val usePathStyle: Boolean = true
) {
@get:JsonIgnore
val endpoint
get() = hostname?.let {
buildString {
append(if (useSsl) "https" else "http", "://", it)
if (port != null) {
append(":", port)
}
}
}
}
override val secretKeySecret: SecretKeySelector,
override val usePathStyle: Boolean = true
) : CloudStorageSpec
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package eu.glasskube.operator.apps.common.cloudstorage

import com.fasterxml.jackson.annotation.JsonIgnore
import io.fabric8.kubernetes.api.model.SecretKeySelector

interface CloudStorageSpec {
val bucket: String
val accessKeySecret: SecretKeySelector
val secretKeySecret: SecretKeySelector
val region: String?
val hostname: String?
val port: Int?
val useSsl: Boolean
val usePathStyle: Boolean?

@get:JsonIgnore
val endpoint
get() = hostname?.let {
buildString {
append(if (useSsl) "https" else "http", "://", it)
if (port != null) {
append(":", port)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eu.glasskube.operator.apps.common.cloudstorage

import com.fasterxml.jackson.annotation.JsonIgnore

interface HasCloudStorageSpec {
@get:JsonIgnore
val cloudStorage: CloudStorageSpec?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package eu.glasskube.operator.apps.common.cloudstorage

import com.fasterxml.jackson.annotation.JsonIgnore

interface ResourceWithCloudStorage {
@get:JsonIgnore
val backupResourceName: String

@get:JsonIgnore
val backupResourceLabels: Map<String, String>

fun getSpec(): HasCloudStorageSpec
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import eu.glasskube.kubernetes.client.resources
import eu.glasskube.operator.Labels
import eu.glasskube.operator.apps.common.backup.ResourceWithBackups
import eu.glasskube.operator.apps.common.cloudstorage.ResourceWithCloudStorage
import eu.glasskube.operator.apps.common.database.ResourceWithDatabaseSpec
import eu.glasskube.operator.apps.common.database.postgres.PostgresDatabaseSpec
import eu.glasskube.operator.apps.gitlab.Gitlab.Postgres.postgresClusterLabelSelector
Expand All @@ -21,6 +22,7 @@ class Gitlab :
CustomResource<GitlabSpec, GitlabStatus>(),
Namespaced,
ResourceWithBackups,
ResourceWithCloudStorage,
ResourceWithDatabaseSpec<PostgresDatabaseSpec> {
companion object {
const val APP_NAME = "gitlab"
Expand All @@ -33,6 +35,9 @@ class Gitlab :
override fun getDatabaseName(primary: Gitlab) = "gitlabhq_production"
}

override val backupResourceName get() = "$genericResourceName-backup"
override val backupResourceLabels get() = resourceLabels

@delegate:JsonIgnore
override val velero by lazy {
object : VeleroNameMapper(this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import eu.glasskube.kubernetes.client.patchOrUpdateStatus
import eu.glasskube.operator.Labels
import eu.glasskube.operator.api.reconciler.getSecondaryResource
import eu.glasskube.operator.api.reconciler.informerEventSource
import eu.glasskube.operator.apps.gitlab.dependent.GitlabCloudStorageBackupCronJob
import eu.glasskube.operator.apps.gitlab.dependent.GitlabConfigMap
import eu.glasskube.operator.apps.gitlab.dependent.GitlabDeployment
import eu.glasskube.operator.apps.gitlab.dependent.GitlabIngress
Expand Down Expand Up @@ -90,6 +91,11 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent
type = GitlabRunners::class,
dependsOn = ["GitlabDeployment"]
),
Dependent(
type = GitlabCloudStorageBackupCronJob::class,
name = "GitlabCloudStorageBackupCronJob",
reconcilePrecondition = GitlabCloudStorageBackupCronJob.ReconcilePrecondition::class
),
Dependent(
type = GitlabVeleroSecret::class,
name = "GitlabVeleroSecret",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package eu.glasskube.operator.apps.gitlab

import com.fasterxml.jackson.annotation.JsonIgnore
import eu.glasskube.operator.apps.common.cloudstorage.CloudStorageSpec
import io.fabric8.generator.annotation.Required
import io.fabric8.kubernetes.api.model.SecretKeySelector

Expand All @@ -8,16 +10,22 @@ data class GitlabRegistryStorageSpec(
) {
data class S3(
@field:Required
val bucket: String,
override val bucket: String,
@field:Required
val accessKeySecret: SecretKeySelector,
override val accessKeySecret: SecretKeySelector,
@field:Required
val secretKeySecret: SecretKeySelector,
override val secretKeySecret: SecretKeySelector,
@field:Required
val region: String,
override val region: String,
@field:Required
val hostname: String,
override val hostname: String,
@field:Required
val usePathStyle: Boolean
)
override val usePathStyle: Boolean
) : CloudStorageSpec {
@field:JsonIgnore
override val port = null

@field:JsonIgnore
override val useSsl = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package eu.glasskube.operator.apps.gitlab

import eu.glasskube.operator.apps.common.backup.BackupSpec
import eu.glasskube.operator.apps.common.backup.HasBackupSpec
import eu.glasskube.operator.apps.common.cloudstorage.HasCloudStorageSpec
import eu.glasskube.operator.apps.common.database.HasDatabaseSpec
import eu.glasskube.operator.apps.common.database.postgres.PostgresDatabaseSpec
import eu.glasskube.operator.validation.Patterns.SEMVER
Expand Down Expand Up @@ -35,4 +36,6 @@ data class GitlabSpec(
@field:Nullable
override val database: PostgresDatabaseSpec = PostgresDatabaseSpec(),
override val backups: BackupSpec?
) : HasBackupSpec, HasDatabaseSpec<PostgresDatabaseSpec>
) : HasBackupSpec, HasCloudStorageSpec, HasDatabaseSpec<PostgresDatabaseSpec> {
override val cloudStorage get() = registry?.storage?.s3
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eu.glasskube.operator.apps.gitlab.dependent

import eu.glasskube.operator.apps.gitlab.Gitlab
import eu.glasskube.operator.apps.gitlab.GitlabReconciler
import eu.glasskube.operator.generic.dependent.backups.DependentCloudStorageBackupCronJob
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent

@KubernetesDependent(labelSelector = GitlabReconciler.SELECTOR)
class GitlabCloudStorageBackupCronJob : DependentCloudStorageBackupCronJob<Gitlab>() {
internal class ReconcilePrecondition : DependentCloudStorageBackupCronJob.ReconcilePrecondition<Gitlab>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import eu.glasskube.kubernetes.api.model.envVar
import eu.glasskube.kubernetes.api.model.secretKeyRef
import eu.glasskube.operator.Labels
import eu.glasskube.operator.apps.common.backup.ResourceWithBackups
import eu.glasskube.operator.apps.common.cloudstorage.ResourceWithCloudStorage
import eu.glasskube.operator.apps.common.database.ResourceWithDatabaseSpec
import eu.glasskube.operator.apps.common.database.postgres.PostgresDatabaseSpec
import eu.glasskube.operator.apps.nextcloud.Nextcloud.Postgres.postgresClusterLabelSelector
Expand All @@ -28,6 +29,7 @@ class Nextcloud :
CustomResource<NextcloudSpec, NextcloudStatus>(),
Namespaced,
ResourceWithBackups,
ResourceWithCloudStorage,
ResourceWithDatabaseSpec<PostgresDatabaseSpec> {
internal companion object {
const val APP_NAME = "nextcloud"
Expand Down Expand Up @@ -58,6 +60,9 @@ class Nextcloud :
override fun getDatabaseName(primary: Nextcloud) = "nextcloud"
}

override val backupResourceName get() = "$genericResourceName-backup"
override val backupResourceLabels get() = resourceLabels

@delegate:JsonIgnore
override val velero by lazy {
object : VeleroNameMapper(this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import eu.glasskube.kubernetes.client.patchOrUpdateStatus
import eu.glasskube.operator.Labels
import eu.glasskube.operator.api.reconciler.getSecondaryResource
import eu.glasskube.operator.api.reconciler.informerEventSource
import eu.glasskube.operator.apps.nextcloud.dependent.NextcloudCloudStorageBackupCronJob
import eu.glasskube.operator.apps.nextcloud.dependent.NextcloudConfigMap
import eu.glasskube.operator.apps.nextcloud.dependent.NextcloudCronJob
import eu.glasskube.operator.apps.nextcloud.dependent.NextcloudDeployment
Expand All @@ -25,9 +26,9 @@ import eu.glasskube.operator.generic.condition.isReady
import eu.glasskube.operator.infra.postgres.PostgresCluster
import eu.glasskube.operator.infra.postgres.isReady
import eu.glasskube.operator.webhook.WebhookService
import eu.glasskube.utils.logger
import io.fabric8.kubernetes.api.model.Service
import io.fabric8.kubernetes.api.model.apps.Deployment
import io.fabric8.kubernetes.api.model.batch.v1.CronJob
import io.javaoperatorsdk.operator.api.reconciler.Context
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext
Expand Down Expand Up @@ -67,7 +68,17 @@ import kotlin.jvm.optionals.getOrDefault
useEventSourceWithName = NextcloudReconciler.DEPLOYMENT_EVENT_SOURCE,
dependsOn = ["NextcloudVolume", "NextcloudConfigMap", "NextcloudPostgresCluster"]
),
Dependent(type = NextcloudCronJob::class, name = "NextcloudCronJob", dependsOn = ["NextcloudDeployment"]),
Dependent(
type = NextcloudCronJob::class,
name = "NextcloudCronJob",
dependsOn = ["NextcloudDeployment"],
useEventSourceWithName = NextcloudReconciler.CRON_JOB_EVENT_SOURCE
),
Dependent(
type = NextcloudCloudStorageBackupCronJob::class,
name = "NextcloudCloudStorageBackupCronJob",
reconcilePrecondition = NextcloudCloudStorageBackupCronJob.ReconcilePrecondition::class
),
Dependent(
type = NextcloudRedisDeployment::class,
name = "NextcloudRedisDeployment",
Expand Down Expand Up @@ -128,7 +139,8 @@ class NextcloudReconciler(webhookService: WebhookService) :
override fun prepareEventSources(context: EventSourceContext<Nextcloud>) = with(context) {
mutableMapOf(
DEPLOYMENT_EVENT_SOURCE to informerEventSource<Deployment>(),
SERVICE_EVENT_SOURCE to informerEventSource<Service>()
SERVICE_EVENT_SOURCE to informerEventSource<Service>(),
CRON_JOB_EVENT_SOURCE to informerEventSource<CronJob>()
)
}

Expand All @@ -142,7 +154,6 @@ class NextcloudReconciler(webhookService: WebhookService) :

internal const val SERVICE_EVENT_SOURCE = "NextcloudServiceEventSource"
internal const val DEPLOYMENT_EVENT_SOURCE = "NextcloudDeploymentEventSource"

private val log = logger()
internal const val CRON_JOB_EVENT_SOURCE = "NextcloudCronJobEventSource"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@ package eu.glasskube.operator.apps.nextcloud

import eu.glasskube.operator.apps.common.backup.BackupSpec
import eu.glasskube.operator.apps.common.backup.HasBackupSpec
import eu.glasskube.operator.apps.common.cloudstorage.CloudStorageSpec
import eu.glasskube.operator.apps.common.cloudstorage.HasCloudStorageSpec
import eu.glasskube.operator.apps.common.database.HasDatabaseSpec
import eu.glasskube.operator.apps.common.database.postgres.PostgresDatabaseSpec
import eu.glasskube.operator.validation.Patterns
import io.fabric8.generator.annotation.Nullable
import io.fabric8.generator.annotation.Pattern
import io.fabric8.generator.annotation.Required
import io.fabric8.kubernetes.api.model.Quantity
import io.fabric8.kubernetes.api.model.ResourceRequirements
import io.fabric8.kubernetes.api.model.SecretKeySelector

data class NextcloudSpec(
val host: String,
val defaultPhoneRegion: String?,
val apps: NextcloudAppsSpec = NextcloudAppsSpec(),
@field:Nullable
val smtp: NextcloudSmtpSpec?,
val storage: NextcloudStorageSpec?,
val storage: StorageSpec?,
@field:Pattern(Patterns.SEMVER)
val version: String = "27.0.1",
val server: ServerSpec = ServerSpec(),
@field:Nullable
override val database: PostgresDatabaseSpec = PostgresDatabaseSpec(),
override val backups: BackupSpec?
) : HasBackupSpec, HasDatabaseSpec<PostgresDatabaseSpec> {
) : HasBackupSpec, HasCloudStorageSpec, HasDatabaseSpec<PostgresDatabaseSpec> {

override val cloudStorage get() = storage?.s3

data class ServerSpec(
@field:Nullable
val resources: ResourceRequirements = ResourceRequirements(
Expand All @@ -36,4 +43,32 @@ data class NextcloudSpec(
val minSpareServers: Int = maxChildren / 16,
val maxSpareServers: Int = maxChildren / 4
)

data class StorageSpec(
val s3: S3?
) {
data class S3(
@field:Required
override val bucket: String,
@field:Required
override val accessKeySecret: SecretKeySelector,
@field:Required
override val secretKeySecret: SecretKeySelector,
@field:Nullable
override val region: String?,
@field:Nullable
override val hostname: String?,
@field:Nullable
override val port: Int?,
@field:Nullable
val objectPrefix: String?,
@field:Nullable
val autoCreate: Boolean?,
override val useSsl: Boolean = true,
@field:Nullable
override val usePathStyle: Boolean?,
@field:Nullable
val legacyAuth: Boolean?
) : CloudStorageSpec
}
}
Loading

0 comments on commit 2b448dc

Please sign in to comment.