Skip to content

Commit a76819b

Browse files
committed
Move user Kustomization to module, allow sequential user Kustomizations
1 parent 2613b1f commit a76819b

File tree

12 files changed

+339
-59
lines changed

12 files changed

+339
-59
lines changed

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,40 @@ _That said, you can also use pure Terraform and import the kube-hetzner module a
334334

335335
<details>
336336

337-
<summary>Custom post-install actions</summary>
337+
<summary>Custom pre- and post-install actions</summary>
338338

339339
After the initial bootstrapping of your Kubernetes cluster, you might want to deploy applications using the same terraform mechanism. For many scenarios it is sufficient to create a `kustomization.yaml.tpl` file (see [Adding Extras](#adding-extras)). All applied kustomizations will be applied at once by executing a single `kubectl apply -k` command.
340340

341-
However, some applications that e.g. provide custom CRDs (e.g. [ArgoCD](https://argoproj.github.io/cd/)) need a different deployment strategy: one has to deploy CRDs first, then wait for the deployment, before being able to install the actual application. In the ArgoCD case, not waiting for the CRD setup to finish will cause failures. Therefore, an additional mechanism is available to support these kind of deployments. Specify `extra_kustomize_deployment_commands` in your `kube.tf` file containing a series of commands to be executed, after the `Kustomization` step finished:
341+
However, some applications that e.g. provide custom CRDs (e.g. [ArgoCD](https://argoproj.github.io/cd/)) need a different deployment strategy: one has to deploy CRDs first, then wait for the deployment, before being able to install the actual application. In the ArgoCD case, not waiting for the CRD setup to finish will cause failures. Therefore, an additional mechanism is available to support these kind of deployments.
342+
343+
### Pre-install Actions, Example: external-secrets repo and Helm
344+
You can install Helm repos and CRDs before the main Kustomization scripts by adding the helm charts to the `extra-manifests-preinstall` folder and specifying the Helm chart in `extra-manifests-preinstall/kustomization.yaml.tpl`, just like with `extra-manifests`.
345+
346+
For example, to add `external-secrets` so that it can be referenced later on, create files:
347+
1. extra-manifests-preinstall/eso.yaml.tpl
348+
```yaml
349+
apiVersion: helm.cattle.io/v1
350+
kind: HelmChart
351+
metadata:
352+
name: external-secrets
353+
namespace: kube-system
354+
spec:
355+
chart: external-secrets
356+
repo: https://charts.external-secrets.io
357+
targetNamespace: external-secrets
358+
createNamespace: true
359+
```
360+
2. extra-manifests-preinstall/kustomization.yaml.tpl
361+
```yaml
362+
apiVersion: kustomize.config.k8s.io/v1beta1
363+
kind: Kustomization
364+
365+
resources:
366+
- eso.yaml
367+
```
368+
369+
### Post-install actions, ArgoCD-example
370+
Specify `extra_kustomize_deployment_commands` in your `kube.tf` file containing a series of commands to be executed, after the `Kustomization` step has finished:
342371

343372
```tf
344373
extra_kustomize_deployment_commands = <<-EOT

kube.tf.example

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,9 +955,34 @@ module "kube-hetzner" {
955955
# Extra commands to be executed after the `kubectl apply -k` (useful for post-install actions, e.g. wait for CRD, apply additional manifests, etc.).
956956
# extra_kustomize_deployment_commands=""
957957

958-
# Extra values that will be passed to the `extra-manifests/kustomization.yaml.tpl` if its present.
958+
# Extra values that will be passed to the `extra-manifests/kustomization.yaml.tpl` and `extra-manifests-preinstall/kustomization.yaml.tpl` when present.
959959
# extra_kustomize_parameters={}
960960

961+
# You can add extra kustomizations to be deployed in sequence by setting the `user_kustomizations` variable.
962+
# This will override the use of `extra_kustomize_deployment_commands` and `extra_kustomize_parameters` for extra-manifests, hence they will need to be reset, see below.
963+
#
964+
# The Kustomization "sets" are run in sequential order (by numeric key) so that you can for example install a CRD and wait for it to be deployed.
965+
#
966+
# source_folder: Sets the source folder for *.yaml.tpl and Kustomization.yaml.tpl
967+
# kustomize_parameters: Key-value map for passing variables into Kustomization. Applies only to the Kustomization-set in the object, but to all files defined in the source_folder of the "set".
968+
# post_commands: Commands to be executed after applying the Kustomization ("kubectl apply -k"). You can use it to wait for CRD deployment etc.
969+
# - An example to wait for deployments in all namespaces: `kubectl wait --for=condition=Available deployment --all -A --timeout=120s || exit 0` (The `|| exit 0` is not "required", it just makes the deployment to continue even if a deployment hasn't started up properly)
970+
# - You can pass full bash-compatible scripts into the `post_commands`-variable with EOT
971+
#
972+
# An example from the default values: (uncomment or copy your own)
973+
# user_kustomizations = {
974+
# "1" = {
975+
# source_folder = "extra-manifests-preinstall"
976+
# kustomize_parameters = {}
977+
# post_commands = ""
978+
# },
979+
# "2" = {
980+
# source_folder = "extra-manifests" # Uses `var.extra_kustomize_folder` internally, defaults to "extra-manifests"
981+
# kustomize_parameters = var.extra_kustomize_parameters # Same as `extra_kustomize_parameters` in kube.tf, replace with actual values if uncommenting
982+
# post_commands = var.extra_kustomize_deployment_commands # Same as `extra_kustomize_deployment_commands` in kube.tf, replace with actual values if uncommenting
983+
# }
984+
# }
985+
961986
# See working examples for extra manifests or a HelmChart in examples/kustomization_user_deploy/README.md
962987

963988
# It is best practice to turn this off, but for backwards compatibility it is set to "true" by default.

kustomization_user.tf

Lines changed: 25 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,34 @@
11
locals {
2-
user_kustomization_templates = try(fileset(var.extra_kustomize_folder, "**/*.yaml.tpl"), toset([]))
3-
}
4-
5-
resource "null_resource" "kustomization_user" {
6-
for_each = local.user_kustomization_templates
7-
8-
connection {
9-
user = "root"
10-
private_key = var.ssh_private_key
11-
agent_identity = local.ssh_agent_identity
12-
host = local.first_control_plane_ip
13-
port = var.ssh_port
14-
15-
bastion_host = local.ssh_bastion.bastion_host
16-
bastion_port = local.ssh_bastion.bastion_port
17-
bastion_user = local.ssh_bastion.bastion_user
18-
bastion_private_key = local.ssh_bastion.bastion_private_key
19-
20-
}
21-
22-
provisioner "remote-exec" {
23-
inline = [
24-
"mkdir -p $(dirname /var/user_kustomize/${each.key})"
25-
]
2+
default_user_kustomize = {
3+
"1" = {
4+
source_folder = "extra-manifests-preinstall"
5+
kustomize_parameters = {}
6+
post_commands = ""
7+
},
8+
"2" = {
9+
source_folder = var.extra_kustomize_folder
10+
kustomize_parameters = var.extra_kustomize_parameters
11+
post_commands = var.extra_kustomize_deployment_commands
12+
}
2613
}
2714

28-
provisioner "file" {
29-
content = templatefile("${var.extra_kustomize_folder}/${each.key}", var.extra_kustomize_parameters)
30-
destination = replace("/var/user_kustomize/${each.key}", ".yaml.tpl", ".yaml")
31-
}
15+
user_kustomize_defaulted = length(var.user_kustomizations) > 0 ? var.user_kustomizations : local.default_user_kustomize
3216

33-
triggers = {
34-
manifest_sha1 = "${sha1(templatefile("${var.extra_kustomize_folder}/${each.key}", var.extra_kustomize_parameters))}"
17+
processed_kustomizes = {
18+
for key, config in local.user_kustomize_defaulted : key => {
19+
# kustomize_parameters may contain secrets
20+
kustomize_parameters = sensitive(config.kustomize_parameters)
21+
source_folder = config.source_folder
22+
post_commands = config.post_commands
23+
}
3524
}
36-
37-
depends_on = [
38-
null_resource.kustomization
39-
]
4025
}
4126

42-
resource "null_resource" "kustomization_user_deploy" {
43-
count = length(local.user_kustomization_templates) > 0 ? 1 : 0
27+
module "user_kustomizations" {
28+
29+
source = "./modules/user_kustomizations"
4430

45-
connection {
31+
ssh_connection = {
4632
user = "root"
4733
private_key = var.ssh_private_key
4834
agent_identity = local.ssh_agent_identity
@@ -53,27 +39,11 @@ resource "null_resource" "kustomization_user_deploy" {
5339
bastion_port = local.ssh_bastion.bastion_port
5440
bastion_user = local.ssh_bastion.bastion_user
5541
bastion_private_key = local.ssh_bastion.bastion_private_key
56-
5742
}
5843

59-
# Remove templates after rendering, and apply changes.
60-
provisioner "remote-exec" {
61-
# Debugging: "sh -c 'for file in $(find /var/user_kustomize -type f -name \"*.yaml\" | sort -n); do echo \"\n### Template $${file}.tpl after rendering:\" && cat $${file}; done'",
62-
inline = compact([
63-
"rm -f /var/user_kustomize/**/*.yaml.tpl",
64-
"echo 'Applying user kustomization...'",
65-
"kubectl apply -k /var/user_kustomize/ --wait=true",
66-
var.extra_kustomize_deployment_commands
67-
])
68-
}
69-
70-
lifecycle {
71-
replace_triggered_by = [
72-
null_resource.kustomization_user
73-
]
74-
}
44+
kustomizations_map = local.processed_kustomizes
7545

7646
depends_on = [
77-
null_resource.kustomization_user
47+
null_resource.kustomization,
7848
]
7949
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
locals {
2+
source_folder_files = try(fileset(var.source_folder, "**/*.yaml.tpl"), toset([]))
3+
source_files_sha = join("", [
4+
for file_path in local.source_folder_files :
5+
filesha1("${var.source_folder}/${file_path}")
6+
])
7+
parameters_sha = sha1(jsonencode(var.template_parameters))
8+
post_commands_string_sha = sha1(var.post_commands_string)
9+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
# Purpose of this module is to copy a single user kustomization "set" to control plane.
3+
# The set contains the yaml-files for Kustomization and the postinstall.sh script.
4+
5+
resource "null_resource" "create_target_directory" {
6+
7+
triggers = {
8+
source_files_sha = local.source_files_sha
9+
parameters_sha = local.parameters_sha
10+
post_commands_string_sha = local.post_commands_string_sha
11+
}
12+
13+
connection {
14+
user = var.ssh_connection.user
15+
private_key = var.ssh_connection.private_key
16+
agent_identity = var.ssh_connection.agent_identity
17+
host = var.ssh_connection.host
18+
port = var.ssh_connection.port
19+
20+
bastion_host = var.ssh_connection.bastion_host
21+
bastion_port = var.ssh_connection.bastion_port
22+
bastion_user = var.ssh_connection.bastion_user
23+
bastion_private_key = var.ssh_connection.bastion_private_key
24+
}
25+
26+
provisioner "remote-exec" {
27+
inline = [
28+
"mkdir -p ${var.destination_folder}"
29+
]
30+
}
31+
32+
provisioner "file" {
33+
content = templatefile("${path.module}/templates/bash.sh.tpl", { commands = var.post_commands_string })
34+
destination = "${var.destination_folder}/postinstall.sh"
35+
}
36+
}
37+
38+
resource "null_resource" "user_kustomization_template_files" {
39+
for_each = local.source_folder_files
40+
41+
lifecycle {
42+
replace_triggered_by = [
43+
null_resource.create_target_directory
44+
]
45+
}
46+
47+
connection {
48+
user = var.ssh_connection.user
49+
private_key = var.ssh_connection.private_key
50+
agent_identity = var.ssh_connection.agent_identity
51+
host = var.ssh_connection.host
52+
port = var.ssh_connection.port
53+
54+
bastion_host = var.ssh_connection.bastion_host
55+
bastion_port = var.ssh_connection.bastion_port
56+
bastion_user = var.ssh_connection.bastion_user
57+
bastion_private_key = var.ssh_connection.bastion_private_key
58+
}
59+
60+
provisioner "file" {
61+
content = templatefile("${var.source_folder}/${each.key}", var.template_parameters)
62+
destination = replace("${var.destination_folder}/${each.key}", ".yaml.tpl", ".yaml")
63+
}
64+
65+
depends_on = [null_resource.create_target_directory]
66+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
output "destination_folder" {
2+
value = var.destination_folder
3+
}
4+
5+
output "source_files_sha" {
6+
value = local.source_files_sha
7+
}
8+
9+
output "parameters_sha" {
10+
value = local.parameters_sha
11+
}
12+
13+
output "post_commands_string_sha" {
14+
value = local.post_commands_string_sha
15+
}
16+
17+
output "files_count" {
18+
description = "Number of template files found in the source folder."
19+
value = length(local.source_folder_files)
20+
}
21+
22+
output "changes_sha" {
23+
value = sha1(join("", [
24+
local.source_files_sha, local.parameters_sha, local.post_commands_string_sha
25+
]))
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
${commands}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
variable "ssh_connection" {
2+
type = object({
3+
user = string
4+
private_key = string
5+
agent_identity = string
6+
host = string
7+
port = string
8+
9+
bastion_host = string
10+
bastion_port = number
11+
bastion_user = string
12+
bastion_private_key = string
13+
})
14+
sensitive = true
15+
}
16+
17+
variable "source_folder" {
18+
type = string
19+
}
20+
21+
variable "destination_folder" {
22+
type = string
23+
default = "/var/user_kustomize"
24+
25+
validation {
26+
condition = startswith(var.destination_folder, "/") && can(regex("^[^\\s]*$", var.destination_folder))
27+
error_message = "destination_folder must start with '/' and must not contain spaces."
28+
}
29+
}
30+
31+
variable "template_parameters" {
32+
type = map(any)
33+
default = {}
34+
sensitive = true
35+
}
36+
37+
variable "post_commands_string" {
38+
type = string
39+
default = ""
40+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
locals {
2+
sorted_kustomization_destination_folders = [
3+
for idx in sort([
4+
for key, mod in module.user_kustomization_set : tonumber(key) if mod.files_count > 0
5+
]) :
6+
module.user_kustomization_set[tostring(idx)].destination_folder
7+
]
8+
base_destination_folder = "/var/user_kustomize"
9+
}

0 commit comments

Comments
 (0)