Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/provs

This commit is contained in:
patdyn 2024-05-30 13:20:52 +02:00
commit a336838af8
33 changed files with 723 additions and 27 deletions

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="test_incl_containtertests" type="JUnit" factoryName="JUnit"> <configuration default="false" name="test_incl_containtertests" type="JUnit" factoryName="JUnit">
<module name="provs.test" /> <module name="org.domaindrivenarchitecture.provs.provs.test" />
<option name="PACKAGE_NAME" value="org" /> <option name="PACKAGE_NAME" value="org" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="test_incl_extensive_container_tests" type="JUnit" factoryName="JUnit"> <configuration default="false" name="test_incl_extensive_container_tests" type="JUnit" factoryName="JUnit">
<module name="provs.test" /> <module name="org.domaindrivenarchitecture.provs.provs.test" />
<option name="PACKAGE_NAME" value="org" /> <option name="PACKAGE_NAME" value="org" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="testquick" type="JUnit" factoryName="JUnit"> <configuration default="false" name="testquick" type="JUnit" factoryName="JUnit">
<module name="provs.test" /> <module name="org.domaindrivenarchitecture.provs.provs.test" />
<option name="PACKAGE_NAME" value="org" /> <option name="PACKAGE_NAME" value="org" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />

View file

@ -1,10 +1,11 @@
FROM ubuntu:latest # image for usage in ci pipeline
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get -y install apt-utils sudo RUN apt-get update && apt-get -y install apt-utils sudo
RUN useradd -m testuser && echo "testuser:testuserpw" | chpasswd && adduser testuser sudo RUN useradd -m testuser && echo "testuser:testuserpw" | chpasswd && usermod -aG sudo testuser
RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser
USER testuser USER testuser

View file

@ -106,6 +106,24 @@ To provision the grafana agent only to an existing k8s system, ensure that the c
provs-server.jar k3s myuser@myhost.com -o grafana provs-server.jar k3s myuser@myhost.com -o grafana
``` ```
To add the hetzner csi driver and encrypted volumes to your k3s installation add the following to the config:
```yaml
hetzner:
hcloudApiToken:
source: "PLAIN" # PLAIN, GOPASS or PROMPT
parameter: "mypassword" # the api key for the hetzner cloud
encryptionPassphrase:
source: "PLAIN" # PLAIN, GOPASS or PROMPT
parameter: "mypassword" # the encryption passphrase for created volumes
```
To provision the grafana agent only to an existing k8s system, ensure that the config (as above) is available and execute:
```bash
provs-server.jar k3s myuser@myhost.com -o grafana
```
Reprovisioning the server can easily be done using the -r or --reprovision option. Reprovisioning the server can easily be done using the -r or --reprovision option.
```bash ```bash

View file

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version_no = "1.7.20" ext.kotlin_version_no = "1.8.20"
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
repositories { repositories {
@ -16,7 +16,7 @@ plugins {
apply plugin: "maven-publish" apply plugin: "maven-publish"
version = "0.31.1-SNAPSHOT" version = "0.35.1-SNAPSHOT"
group = "org.domaindrivenarchitecture.provs" group = "org.domaindrivenarchitecture.provs"
@ -76,8 +76,8 @@ dependencies {
api('com.charleskorn.kaml:kaml:0.54.0') api('com.charleskorn.kaml:kaml:0.54.0')
api("org.slf4j:slf4j-api:1.7.36") api("org.slf4j:slf4j-api:1.7.36")
api('ch.qos.logback:logback-classic:1.2.11') api('ch.qos.logback:logback-classic:1.4.14')
api('ch.qos.logback:logback-core:1.2.11') api('ch.qos.logback:logback-core:1.4.14')
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version_no") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version_no")
implementation("com.hierynomus:sshj:0.32.0") implementation("com.hierynomus:sshj:0.32.0")

View file

@ -8,7 +8,7 @@ name = "provs"
PROJECT_ROOT_PATH = "." PROJECT_ROOT_PATH = "."
version = "0.31.1-SNAPSHOT" version = "0.35.1-dev"
@init @init
@ -38,6 +38,7 @@ def initialize2(project):
"project_root_path": PROJECT_ROOT_PATH, "project_root_path": PROJECT_ROOT_PATH,
"build_types": [], "build_types": [],
"mixin_types": ["RELEASE"], "mixin_types": ["RELEASE"],
"release_main_branch": "main",
"release_primary_build_file": "build.gradle", "release_primary_build_file": "build.gradle",
"release_secondary_build_files": ["build.py"], "release_secondary_build_files": ["build.py"],
# release artifacts # release artifacts

View file

@ -5,7 +5,7 @@ release-1.2 or release-1.2.3
I.e.: release-X.X.Z where X, Y, Z are the major, minor resp. the patch level of the release. Z can be omitted. I.e.: release-X.X.Z where X, Y, Z are the major, minor resp. the patch level of the release. Z can be omitted.
**Note:** Such kind of release tags should only be applied to commits in the master branch. **Note:** Such kind of release tags should only be applied to commits in the main branch.
``` ```
#adjust [version] #adjust [version]

View file

@ -0,0 +1,10 @@
### Howto update gradle wrapper
1. To *latest* version (be aware for deprecated parts in future versions):
```shell
./gradlew wrapper --gradle-version latest
```
2. To *specific version:
```shell
./gradlew wrapper --gradle-version 8.6
```

View file

@ -161,4 +161,6 @@ fun Prov.provisionIdeDesktop() {
// IDEs // IDEs
installVSC("python", "clojure") installVSC("python", "clojure")
installIntelliJ() installIntelliJ()
installKubeconform()
} }

View file

@ -49,9 +49,33 @@ fun Prov.installKubectlAndTools(): ProvResult = task {
} }
} }
task("installKubeconform") {
installKubeconform()
}
installDevopsScripts() installDevopsScripts()
} }
fun Prov.installKubeconform() = task {
// check for latest stable release on: https://github.com/yannh/kubeconform/releases
val version = "0.6.4"
val installationPath = "/usr/local/bin/"
val tmpDir = "~/tmp"
val filename = "kubeconform-linux-amd64"
val packedFilename = "$filename.tar.gz"
if ( !chk("kubeconform -v") || "v$version" != cmd("kubeconform -v").out?.trim() ) {
downloadFromURL(
"https://github.com/yannh/kubeconform/releases/download/v$version/$packedFilename",
path = tmpDir,
sha256sum = "2b4ebeaa4d5ac4843cf8f7b7e66a8874252b6b71bc7cbfc4ef1cbf85acec7c07"
)
cmd("sudo tar -xzf $packedFilename -C $installationPath", tmpDir)
} else {
ProvResult(true, out = "Kubeconform $version already installed")
}
}
fun Prov.installKubectl(): ProvResult = task { fun Prov.installKubectl(): ProvResult = task {
// see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/ // see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/

View file

@ -17,12 +17,12 @@ class UbuntuPlusUser(private val userName: String = "testuser") : DockerImage {
override fun imageText(): String { override fun imageText(): String {
return """ return """
FROM ubuntu:20.04 FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get -y install sudo RUN apt-get update && apt-get -y install sudo
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && usermod -aG sudo $userName
RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName
USER $userName USER $userName

View file

@ -0,0 +1,7 @@
package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi
import org.domaindrivenarchitecture.provs.framework.core.Prov
import org.domaindrivenarchitecture.provs.server.infrastructure.provisionHetznerCSIForK8s
fun Prov.provisionHetznerCSI(configResolved: HetznerCSIConfigResolved) =
provisionHetznerCSIForK8s(configResolved.hcloudApiToken, configResolved.encryptionPassphrase)

View file

@ -0,0 +1,23 @@
package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi
import kotlinx.serialization.Serializable
import org.domaindrivenarchitecture.provs.framework.core.Secret
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier
@Serializable
data class HetznerCSIConfig (
val hcloudApiToken: SecretSupplier,
val encryptionPassphrase: SecretSupplier,
) {
fun resolveSecret(): HetznerCSIConfigResolved = HetznerCSIConfigResolved(this)
}
data class HetznerCSIConfigResolved(val configUnresolved: HetznerCSIConfig) {
val hcloudApiToken: Secret = configUnresolved.hcloudApiToken.secret()
val encryptionPassphrase: Secret = configUnresolved.encryptionPassphrase.secret()
}
@Serializable
data class HetznerCSIConfigHolder(
val hetzner: HetznerCSIConfig
)

View file

@ -2,6 +2,8 @@ package org.domaindrivenarchitecture.provs.server.domain.k3s
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
import org.domaindrivenarchitecture.provs.framework.core.Prov import org.domaindrivenarchitecture.provs.framework.core.Prov
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigResolved
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.provisionHetznerCSI
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfigResolved import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfigResolved
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.provisionGrafanaAgent import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.provisionGrafanaAgent
import org.domaindrivenarchitecture.provs.server.infrastructure.* import org.domaindrivenarchitecture.provs.server.infrastructure.*
@ -11,6 +13,7 @@ import kotlin.system.exitProcess
fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task { fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret() val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret()
val hcloudConfigResolved: HetznerCSIConfigResolved? = findHetznerCSIConfig(cli.configFileName)?.resolveSecret()
if (cli.onlyModules == null) { if (cli.onlyModules == null) {
val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName) val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName)
@ -18,9 +21,10 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision) val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision)
val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() } val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() }
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, applicationFile) provisionK3s(k3sConfigReprovision, grafanaConfigResolved, hcloudConfigResolved, applicationFile)
} else { } else {
provisionGrafana(cli.onlyModules, grafanaConfigResolved) provisionGrafana(cli.onlyModules, grafanaConfigResolved)
provisionHetznerCSI(cli.onlyModules, hcloudConfigResolved)
} }
} }
@ -30,6 +34,7 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
fun Prov.provisionK3s( fun Prov.provisionK3s(
k3sConfig: K3sConfig, k3sConfig: K3sConfig,
grafanaConfigResolved: GrafanaAgentConfigResolved? = null, grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
hetznerCSIConfigResolved: HetznerCSIConfigResolved? = null,
applicationFile: ApplicationFile? = null applicationFile: ApplicationFile? = null
) = task { ) = task {
@ -53,6 +58,10 @@ fun Prov.provisionK3s(
provisionGrafanaAgent(grafanaConfigResolved) provisionGrafanaAgent(grafanaConfigResolved)
} }
if (hetznerCSIConfigResolved != null) {
provisionHetznerCSI(hetznerCSIConfigResolved)
}
if (applicationFile != null) { if (applicationFile != null) {
provisionK3sApplication(applicationFile) provisionK3sApplication(applicationFile)
} }
@ -75,3 +84,18 @@ private fun Prov.provisionGrafana(
provisionGrafanaAgent(grafanaConfigResolved) provisionGrafanaAgent(grafanaConfigResolved)
} }
} }
private fun Prov.provisionHetznerCSI(
onlyModules: List<String>?,
hetznerCSIConfigResolved: HetznerCSIConfigResolved?
) = task {
if (onlyModules != null && onlyModules.contains(ServerOnlyModule.HETZNER_CSI.name.lowercase())) {
if (hetznerCSIConfigResolved == null) {
println("ERROR: Could not find grafana config.")
exitProcess(7)
}
provisionHetznerCSI(hetznerCSIConfigResolved)
}
}

View file

@ -1,5 +1,6 @@
package org.domaindrivenarchitecture.provs.server.domain.k3s package org.domaindrivenarchitecture.provs.server.domain.k3s
enum class ServerOnlyModule { enum class ServerOnlyModule {
GRAFANA GRAFANA,
HETZNER_CSI
} }

View file

@ -0,0 +1,53 @@
package org.domaindrivenarchitecture.provs.server.infrastructure
import org.domaindrivenarchitecture.provs.framework.core.Prov
import org.domaindrivenarchitecture.provs.framework.core.Secret
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResourceTemplate
import org.domaindrivenarchitecture.provs.server.domain.k3s.FileMode
import java.io.File
private const val hetznerCSIResourceDir = "org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/"
fun Prov.provisionHetznerCSIForK8s(hetznerApiToken: Secret, encryptionPassphrase: Secret) {
// CSI Driver
createFileFromResourceTemplate(
k3sManualManifestsDir + "hcloud-api-token-secret.yaml",
"hcloud-api-token-secret.template.yaml",
resourcePath = hetznerCSIResourceDir,
posixFilePermission = "644",
values = mapOf(
"HETZNER_API_TOKEN" to hetznerApiToken.plain()
))
cmd("kubectl apply -f hcloud-api-token-secret.yaml", k3sManualManifestsDir)
applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-csi.yaml"))
// Encryption
createFileFromResourceTemplate(
k3sManualManifestsDir + "hcloud-encryption-secret.yaml",
"hcloud-encryption-secret.template.yaml",
resourcePath = hetznerCSIResourceDir,
posixFilePermission = "644",
values = mapOf(
"HETZNER_ENCRYPTION_PASSPHRASE" to encryptionPassphrase.plain()
))
cmd("kubectl apply -f hcloud-encryption-secret.yaml", k3sManualManifestsDir)
applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-encrypted-storage-class.yaml"))
}
private fun Prov.createHetznerCSIFileFromResource(
file: File,
posixFilePermission: FileMode? = "644"
) = task {
createFileFromResource(
file.path,
file.name,
hetznerCSIResourceDir,
posixFilePermission,
sudo = true
)
}
private fun Prov.applyHetznerCSIFileFromResource(file: File, posixFilePermission: FileMode? = "644") = task {
createHetznerCSIFileFromResource(file, posixFilePermission)
cmd("kubectl apply -f ${file.path}", sudo = true)
}

View file

@ -0,0 +1,31 @@
package org.domaindrivenarchitecture.provs.server.infrastructure
import com.charleskorn.kaml.MissingRequiredPropertyException
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
import org.domaindrivenarchitecture.provs.framework.core.readFromFile
import org.domaindrivenarchitecture.provs.framework.core.toYaml
import org.domaindrivenarchitecture.provs.framework.core.yamlToType
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigHolder
import java.io.File
import java.io.FileWriter
private const val DEFAULT_CONFIG_FILE = "server-config.yaml"
fun findHetznerCSIConfig(fileName: ConfigFileName? = null): HetznerCSIConfig? {
val filePath = fileName?.fileName ?: DEFAULT_CONFIG_FILE
return if(File(filePath).exists()) {
try {
readFromFile(filePath).yamlToType<HetznerCSIConfigHolder>().hetzner
} catch (e: MissingRequiredPropertyException) {
if (e.message.contains("Property 'hetzner'")) null else throw e
}
} else {
null
}
}
@Suppress("unused")
internal fun writeConfig(config: HetznerCSIConfigHolder, fileName: String = "hetzner-config.yaml") =
FileWriter(fileName).use { it.write(config.toYaml()) }

View file

@ -39,7 +39,6 @@ private val selfSignedCertificate = File(k3sManualManifestsDir, "selfsigned-cert
private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml") private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml")
// ----------------------------------- public functions -------------------------------- // ----------------------------------- public functions --------------------------------
fun Prov.testConfigExists(): Boolean { fun Prov.testConfigExists(): Boolean {
@ -52,7 +51,11 @@ fun Prov.deprovisionK3sInfra() = task {
deleteFile(certManagerDeployment.path, sudo = true) deleteFile(certManagerDeployment.path, sudo = true)
deleteFile(certManagerIssuer.path, sudo = true) deleteFile(certManagerIssuer.path, sudo = true)
deleteFile(k3sKubeConfig.path, sudo = true) deleteFile(k3sKubeConfig.path, sudo = true)
cmd("k3s-uninstall.sh")
val k3sUninstallScript = "k3s-uninstall.sh"
if (chk("which $k3sUninstallScript")) {
cmd(k3sUninstallScript)
}
} }
@ -120,6 +123,7 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult {
applyK3sFileFromResource(k3sMiddleWareHttpsRedirect) applyK3sFileFromResource(k3sMiddleWareHttpsRedirect)
} }
// other
applyK3sFileFromResource(localPathProvisionerConfig) applyK3sFileFromResource(localPathProvisionerConfig)
cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true) cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true)

View file

@ -8,7 +8,7 @@ function usage() {
function main() { function main() {
local cluster_name="${1}"; local cluster_name="${1}";
local domain_name="${2:-meissa-gmbh.de}"; local domain_name="${2:-meissa.de}";
/usr/local/bin/k3s-create-context.sh ${cluster_name} ${domain_name} /usr/local/bin/k3s-create-context.sh ${cluster_name} ${domain_name}
kubectl config use-context ${cluster_name} kubectl config use-context ${cluster_name}

View file

@ -4,8 +4,9 @@ set -o noglob
function main() { function main() {
local cluster_name="${1}"; shift local cluster_name="${1}"; shift
local domain_name="${1:-meissa.de}"; shift
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.meissa-gmbh.de -L 8002:localhost:8002 -L 6443:192.168.5.1:6443 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.${domain_name} -L 8002:localhost:8002 -L 6443:192.168.5.1:6443
} }
main $1 main $1

View file

@ -4,8 +4,9 @@ set -o noglob
function main() { function main() {
local cluster_name="${1}"; shift local cluster_name="${1}"; shift
local domain_name="${1:-meissa.de}"; shift
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.meissa-gmbh.de ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.${domain_name}
} }
main $1 main $1

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: hcloud
namespace: kube-system
stringData:
token: $HETZNER_API_TOKEN

View file

@ -0,0 +1,401 @@
# Version 2.6.0
# Source: hcloud-csi/templates/controller/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: hcloud-csi-controller
namespace: "kube-system"
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
automountServiceAccountToken: true
---
# Source: hcloud-csi/templates/core/storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: hcloud-volumes
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: csi.hetzner.cloud
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: "Delete"
---
# Source: hcloud-csi/templates/controller/clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: hcloud-csi-controller
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
rules:
# attacher
- apiGroups: [""]
resources: [persistentvolumes]
verbs: [get, list, watch, update, patch]
- apiGroups: [""]
resources: [nodes]
verbs: [get, list, watch]
- apiGroups: [csi.storage.k8s.io]
resources: [csinodeinfos]
verbs: [get, list, watch]
- apiGroups: [storage.k8s.io]
resources: [csinodes]
verbs: [get, list, watch]
- apiGroups: [storage.k8s.io]
resources: [volumeattachments]
verbs: [get, list, watch, update, patch]
- apiGroups: [storage.k8s.io]
resources: [volumeattachments/status]
verbs: [patch]
# provisioner
- apiGroups: [""]
resources: [secrets]
verbs: [get, list]
- apiGroups: [""]
resources: [persistentvolumes]
verbs: [get, list, watch, create, delete, patch]
- apiGroups: [""]
resources: [persistentvolumeclaims, persistentvolumeclaims/status]
verbs: [get, list, watch, update, patch]
- apiGroups: [storage.k8s.io]
resources: [storageclasses]
verbs: [get, list, watch]
- apiGroups: [""]
resources: [events]
verbs: [list, watch, create, update, patch]
- apiGroups: [snapshot.storage.k8s.io]
resources: [volumesnapshots]
verbs: [get, list]
- apiGroups: [snapshot.storage.k8s.io]
resources: [volumesnapshotcontents]
verbs: [get, list]
# resizer
- apiGroups: [""]
resources: [pods]
verbs: [get, list, watch]
# node
- apiGroups: [""]
resources: [events]
verbs: [get, list, watch, create, update, patch]
---
# Source: hcloud-csi/templates/controller/clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: hcloud-csi-controller
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: hcloud-csi-controller
subjects:
- kind: ServiceAccount
name: hcloud-csi-controller
namespace: "kube-system"
---
# Source: hcloud-csi/templates/controller/service.yaml
apiVersion: v1
kind: Service
metadata:
name: hcloud-csi-controller-metrics
namespace: "kube-system"
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
spec:
ports:
- name: metrics
port: 9189
selector:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
---
# Source: hcloud-csi/templates/node/service.yaml
apiVersion: v1
kind: Service
metadata:
name: hcloud-csi-node-metrics
namespace: "kube-system"
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: node
spec:
ports:
- name: metrics
port: 9189
selector:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: node
---
# Source: hcloud-csi/templates/node/daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: hcloud-csi-node
namespace: "kube-system"
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: node
app: hcloud-csi
spec:
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: hcloud-csi
template:
metadata:
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: node
app: hcloud-csi
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: instance.hetzner.cloud/is-root-server
operator: NotIn
values:
- "true"
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
- key: CriticalAddonsOnly
operator: Exists
securityContext:
fsGroup: 1001
initContainers:
containers:
- name: csi-node-driver-registrar
image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.7.0
imagePullPolicy: IfNotPresent
args:
- --kubelet-registration-path=/var/lib/kubelet/plugins/csi.hetzner.cloud/socket
volumeMounts:
- name: plugin-dir
mountPath: /run/csi
- name: registration-dir
mountPath: /registration
resources:
limits: {}
requests: {}
- name: liveness-probe
image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /run/csi
name: plugin-dir
resources:
limits: {}
requests: {}
- name: hcloud-csi-driver
image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version
imagePullPolicy: IfNotPresent
command: [/bin/hcloud-csi-driver-node]
volumeMounts:
- name: kubelet-dir
mountPath: /var/lib/kubelet
mountPropagation: "Bidirectional"
- name: plugin-dir
mountPath: /run/csi
- name: device-dir
mountPath: /dev
securityContext:
privileged: true
env:
- name: CSI_ENDPOINT
value: unix:///run/csi/socket
- name: METRICS_ENDPOINT
value: "0.0.0.0:9189"
- name: ENABLE_METRICS
value: "true"
ports:
- containerPort: 9189
name: metrics
- name: healthz
protocol: TCP
containerPort: 9808
resources:
limits: {}
requests: {}
livenessProbe:
failureThreshold: 5
initialDelaySeconds: 10
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
httpGet:
path: /healthz
port: healthz
volumes:
- name: kubelet-dir
hostPath:
path: /var/lib/kubelet
type: Directory
- name: plugin-dir
hostPath:
path: /var/lib/kubelet/plugins/csi.hetzner.cloud/
type: DirectoryOrCreate
- name: registration-dir
hostPath:
path: /var/lib/kubelet/plugins_registry/
type: Directory
- name: device-dir
hostPath:
path: /dev
type: Directory
---
# Source: hcloud-csi/templates/controller/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hcloud-csi-controller
namespace: "kube-system"
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
app: hcloud-csi-controller
spec:
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app: hcloud-csi-controller
template:
metadata:
labels:
app.kubernetes.io/name: hcloud-csi
app.kubernetes.io/instance: hcloud-csi
app.kubernetes.io/component: controller
app: hcloud-csi-controller
spec:
serviceAccountName: hcloud-csi-controller
securityContext:
fsGroup: 1001
initContainers:
containers:
- name: csi-attacher
image: registry.k8s.io/sig-storage/csi-attacher:v4.1.0
imagePullPolicy: IfNotPresent
resources:
limits: {}
requests: {}
args:
- --default-fstype=ext4
volumeMounts:
- name: socket-dir
mountPath: /run/csi
- name: csi-resizer
image: registry.k8s.io/sig-storage/csi-resizer:v1.7.0
imagePullPolicy: IfNotPresent
resources:
limits: {}
requests: {}
volumeMounts:
- name: socket-dir
mountPath: /run/csi
- name: csi-provisioner
image: registry.k8s.io/sig-storage/csi-provisioner:v3.4.0
imagePullPolicy: IfNotPresent
resources:
limits: {}
requests: {}
args:
- --feature-gates=Topology=true
- --default-fstype=ext4
volumeMounts:
- name: socket-dir
mountPath: /run/csi
- name: liveness-probe
image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0
imagePullPolicy: IfNotPresent
resources:
limits: {}
requests: {}
volumeMounts:
- mountPath: /run/csi
name: socket-dir
- name: hcloud-csi-driver
image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version
imagePullPolicy: IfNotPresent
command: [/bin/hcloud-csi-driver-controller]
env:
- name: CSI_ENDPOINT
value: unix:///run/csi/socket
- name: METRICS_ENDPOINT
value: "0.0.0.0:9189"
- name: ENABLE_METRICS
value: "true"
- name: KUBE_NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: HCLOUD_TOKEN
valueFrom:
secretKeyRef:
name: hcloud
key: token
resources:
limits: {}
requests: {}
ports:
- name: metrics
containerPort: 9189
- name: healthz
protocol: TCP
containerPort: 9808
livenessProbe:
failureThreshold: 5
initialDelaySeconds: 10
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
httpGet:
path: /healthz
port: healthz
volumeMounts:
- name: socket-dir
mountPath: /run/csi
volumes:
- name: socket-dir
emptyDir: {}
---
# Source: hcloud-csi/templates/core/csidriver.yaml
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: csi.hetzner.cloud
spec:
attachRequired: true
fsGroupPolicy: File
podInfoOnMount: true
volumeLifecycleModes:
- Persistent

View file

@ -0,0 +1,11 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: hcloud-volumes-encrypted
provisioner: csi.hetzner.cloud
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
csi.storage.k8s.io/node-publish-secret-name: encryption-secret
csi.storage.k8s.io/node-publish-secret-namespace: kube-system

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: encryption-secret
namespace: kube-system
stringData:
encryption-passphrase: $HETZNER_ENCRYPTION_PASSPHRASE

View file

@ -2,9 +2,8 @@ kind: Ingress
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
metadata: metadata:
name: echo-ingress name: echo-ingress
annotations:
kubernetes.io/ingress.class: "traefik"
spec: spec:
ingressClassName: traefik
rules: rules:
- http: - http:
paths: paths:

View file

@ -3,9 +3,9 @@ apiVersion: networking.k8s.io/v1
metadata: metadata:
name: echo-ingress name: echo-ingress
annotations: annotations:
kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: ${issuer_name} cert-manager.io/cluster-issuer: ${issuer_name}
spec: spec:
ingressClassName: traefik
rules: rules:
- host: ${fqdn} - host: ${fqdn}
http: http:

View file

@ -6,6 +6,7 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.creat
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText
import org.domaindrivenarchitecture.provs.test.defaultTestContainer import org.domaindrivenarchitecture.provs.test.defaultTestContainer
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
@ -48,4 +49,17 @@ internal class DevOpsKtTest {
// then // then
assertTrue(res.success) assertTrue(res.success)
} }
@ContainerTest
fun installKubeconform() {
// given
val prov = defaultTestContainer()
// when
val res = prov.installKubeconform()
// then
assertTrue(res.success)
assertTrue(prov.checkFile("/usr/local/bin/kubeconform"))
}
} }

View file

@ -103,7 +103,8 @@ internal class UbuntuProvTest {
// then // then
assertFalse(result.success) assertFalse(result.success)
assertEquals("sudo: no tty present and no askpass program specified\n", result.err) val expectedMsg = "a password is required"
assertTrue(result.err?.contains(expectedMsg) ?: false, "Error: [$expectedMsg] is not found in [${result.err}]")
} }
} }
@ -119,12 +120,12 @@ class UbuntuUserNeedsPasswordForSudo(private val userName: String = "testuser")
override fun imageText(): String { override fun imageText(): String {
return """ return """
FROM ubuntu:18.04 FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get -y install sudo RUN apt-get update && apt-get -y install sudo
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && usermod -aG sudo $userName
USER $userName USER $userName
CMD /bin/bash CMD /bin/bash

View file

@ -0,0 +1,35 @@
package org.domaindrivenarchitecture.provs.server.infrastructure
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfig
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
internal class HetznerCSIRepositoryKtTest {
@Test
fun findHetznerCSIConfig_returns_config() {
// when
val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config-with-hetzner.yaml"))
// then
assertEquals(
HetznerCSIConfig(
hcloudApiToken = SecretSupplier(SecretSourceType.GOPASS, "path/to/apitoken"),
encryptionPassphrase = SecretSupplier(SecretSourceType.GOPASS, "path/to/encryption"),
), config
)
}
@Test
fun findHetznerCSIConfig_returns_null_if_no_hetzner_data_available() {
// when
val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config.yaml"))
// then
assertEquals(null, config)
}
}

View file

@ -0,0 +1,18 @@
fqdn: statistics.test.meissa-gmbh.de
node:
ipv4: 162.55.164.138
ipv6: 2a01:4f8:c010:672f::1
certmanager:
email: admin@meissa-gmbh.de
letsencryptEndpoint: prod
echo: true
reprovision: true
hetzner:
hcloudApiToken:
source: "GOPASS" # PLAIN, GOPASS or PROMPT
parameter: "path/to/apitoken" # the api key for the hetzner cloud
encryptionPassphrase:
source: "GOPASS" # PLAIN, GOPASS or PROMPT
parameter: "path/to/encryption" # the encryption passphrase for created volumes

View file

@ -16,7 +16,9 @@ const val defaultTestContainerName = "provs_test"
private lateinit var prov: Prov private lateinit var prov: Prov
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov { fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
if (!::prov.isInitialized || !testLocal().containerRuns(defaultTestContainerName) || (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING)) { prov = initDefaultTestContainer(startMode) } if (!::prov.isInitialized || !testLocal().containerRuns(defaultTestContainerName) || (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING)) {
prov = initDefaultTestContainer(startMode)
}
return prov return prov
} }