Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cruft.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "solution-backup",
"slug": "solution-backup",
"parameter_key": "solution_backup",
"test_cases": "defaults",
"test_cases": "defaults override-schedules override-labels",
"add_lib": "n",
"add_pp": "n",
"add_golden": "y",
Expand Down
1 change: 0 additions & 1 deletion .github/changelog-configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,3 @@
],
"template": "${{CATEGORIZED_COUNT}} changes since ${{FROM_TAG}}\n\n${{CHANGELOG}}"
}

20 changes: 20 additions & 0 deletions .github/workflows/espejote.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Espejote render
on:
pull_request:
push:

env:
ESPEJOTE_VERSION: 0.19.0

jobs:
espejote:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install espejote ${{ env.ESPEJOTE_VERSION }}
run: |
set -euo pipefail
curl -sL "https://github.com/vshn/espejote/releases/download/v${ESPEJOTE_VERSION}/espejote_${ESPEJOTE_VERSION}_linux_amd64.tar.gz" | tar xz espejote
sudo install -m 0755 espejote /usr/local/bin/espejote
- name: Render and diff expected files
run: make test-espejote
4 changes: 4 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
matrix:
instance:
- defaults
- override-schedules
- override-labels
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand All @@ -48,6 +50,8 @@ jobs:
matrix:
instance:
- defaults
- override-schedules
- override-labels
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SHELL := bash
.SUFFIXES:

include Makefile.vars.mk
-include Makefile.custom.mk

.PHONY: help
help: ## Show this help
Expand Down
11 changes: 11 additions & 0 deletions Makefile.custom.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Configure instance alias for commodore component compile
commodore_args += --alias $(instance)

.PHONY: test-espejote
test-espejote:
bash tests/render/run.sh
git diff --exit-code -- tests/render/expected/

.PHONY: gen-espejote
gen-espejote:
bash tests/render/run.sh
2 changes: 1 addition & 1 deletion Makefile.vars.mk
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ KUBENT_IMAGE ?= ghcr.io/doitintl/kube-no-trouble:latest
KUBENT_DOCKER ?= $(DOCKER_CMD) $(DOCKER_ARGS) $(root_volume) --entrypoint=/app/kubent $(KUBENT_IMAGE)

instance ?= defaults
test_instances = tests/defaults.yml
test_instances = tests/defaults.yml tests/override-schedules.yml tests/override-labels.yml
31 changes: 30 additions & 1 deletion class/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,33 @@ parameters:
solution_backup:
=_metadata:
multi_tenant: true
namespace: syn-solution-backup
multi_version: true
multi_instance: true
Comment thread
BacLuc marked this conversation as resolved.
namespace: ${_instance}

createNamespace: true
namespaceAnnotations: {}
namespaceLabels: {}

backup_namespaces:
label: vshn.ch/solution-base-backup
value: "true"

backup_encryption_secret:
label: vshn.ch/solution-base-backup-encryption-secret

s3:
endpoint: https://objects.%(region)s.cloudscale.ch
region: rma

objectbucket:
name_prefix: pvc-backup-

schedule:
backup: "0 1 * * *"
check: "0 3 * * *"
prune: "0 4 * * *"
retention:
keepDaily: 3
keepWeekly: 2
keepMonthly: 1
13 changes: 11 additions & 2 deletions component/app.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ local inv = kap.inventory();
local params = inv.parameters.solution_backup;
local argocd = import 'lib/argocd.libjsonnet';

local app = argocd.App('solution-backup', params.namespace);
local instance = inv.parameters._instance;
local app = argocd.App(instance, params.namespace, secrets=true, base='solution_backup') {
spec+: {
syncPolicy+: {
syncOptions+: [
'ServerSideApply=true',
],
},
},
};

local appPath =
local project = std.get(std.get(app, 'spec', {}), 'project', 'syn');
if project == 'syn' then 'apps' else 'apps-%s' % project;

{
['%s/solution-backup' % appPath]: app,
['%s/%s' % [ appPath, instance ]]: app,
}
208 changes: 205 additions & 3 deletions component/main.jsonnet
Original file line number Diff line number Diff line change
@@ -1,10 +1,212 @@
// main template for solution-backup
local kap = import 'lib/kapitan.libjsonnet';
local kube = import 'lib/kube.libjsonnet';

local inv = kap.inventory();
// The hiera parameters for the component
local params = inv.parameters.solution_backup;
local instance = inv.parameters._instance;
local slug = std.substr(std.sha256(instance), 0, 8);

local nsName = params.namespace;
local objectPrefix = 'pvc-backup-' + slug;
local saName = 'solution-backup-manager';

local backupNs = params.backup_namespaces;
local encryption = params.backup_encryption_secret;

local s3 = params.s3;
local ob = params.objectbucket;
local sched = params.schedule;
local endpointResolved = s3.endpoint % { region: s3.region };

local namespace = (if params.createNamespace then kube.Namespace(nsName) else null) + {
metadata+: {
labels+: params.namespaceLabels,
annotations+: params.namespaceAnnotations,
},
};

local serviceAccount = kube.ServiceAccount(saName) {
metadata+: { namespace: nsName },
};
local clusterRole = {
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRole',
metadata: {
name: nsName + ':' + saName,
},
rules: [
{
apiGroups: [ '' ],
resources: [ 'namespaces' ],
verbs: [ 'get', 'list', 'watch' ],
},
{
apiGroups: [ 'appcat.vshn.io' ],
resources: [ 'objectbuckets' ],
verbs: [ '*' ],
},
{
apiGroups: [ 'k8up.io' ],
resources: [ 'schedules' ],
verbs: [ '*' ],
},
],
};
local clusterRoleBinding = {
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRoleBinding',
metadata: {
name: nsName + ':' + saName,
},
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: clusterRole.metadata.name,
},
subjects: [
{
kind: 'ServiceAccount',
name: saName,
namespace: nsName,
},
],
};

local rawTemplate = |||
local esp = import 'espejote.libsonnet';

local inDelete(obj) = std.get(obj.metadata, 'deletionTimestamp', '') != '';
local wantsBackup(ns) =
local labels = std.get(ns.metadata, 'labels', {});
!inDelete(ns)
&& std.get(labels, '%(optInLabel)s', 'false') == '%(optInValue)s'
&& std.objectHas(labels, '%(encryptionSecretNameLabel)s');

local managedBy = { 'espejote.io/managed-by': '%(managedBy)s' };
local credsSecretName = '%(credsSecretName)s';
local bucketNamePrefix = '%(bucketNamePrefix)s';

local encryptionSecretName(ns) =
std.get(ns.metadata, 'labels', {})['%(encryptionSecretNameLabel)s'];

local nsSlug(ns) = std.substr(std.sha256(ns.metadata.name), 0, 8);

local makeObjectBucket(ns) = {
apiVersion: 'appcat.vshn.io/v1',
kind: 'ObjectBucket',
metadata: {
name: '%(objectPrefix)s',
namespace: ns.metadata.name,
labels: managedBy
},
spec: {
parameters: {
bucketName: bucketNamePrefix + nsSlug(ns),
region: '%(region)s'
},
writeConnectionSecretToRef: {
name: credsSecretName
},
},
};

local makeSchedule(ns) = {
apiVersion: 'k8up.io/v1',
kind: 'Schedule',
metadata: {
name: '%(objectPrefix)s',
namespace: ns.metadata.name,
labels: managedBy
},
spec: {
backend: {
repoPasswordSecretRef: {
name: encryptionSecretName(ns),
key: 'password'
},
s3: {
endpoint: '%(endpoint)s',
bucket: bucketNamePrefix + nsSlug(ns),
accessKeyIDSecretRef: {
name: credsSecretName,
key: 'AWS_ACCESS_KEY_ID'
},
secretAccessKeySecretRef: {
name: credsSecretName,
key: 'AWS_SECRET_ACCESS_KEY'
},
},
},
backup: { schedule: '%(backupSchedule)s' },
check: { schedule: '%(checkSchedule)s' },
prune: { schedule: '%(pruneSchedule)s', retention: %(retention)s },
},
};

local manageNs(ns) = [ makeObjectBucket(ns), makeSchedule(ns) ];

local tn = esp.triggerName();
if tn == 'namespace' then (
local ns = esp.triggerData().resource;
if ns != null && wantsBackup(ns) then manageNs(ns) else []
) else [
Comment thread
martini-source marked this conversation as resolved.
obj
for ns in esp.context().backup_namespaces
if wantsBackup(ns)
for obj in manageNs(ns)
Comment thread
martini-source marked this conversation as resolved.
]
|||;
local template = rawTemplate % {
optInLabel: backupNs.label,
optInValue: backupNs.value,
managedBy: nsName + '.' + objectPrefix,
objectPrefix: objectPrefix,
credsSecretName: 'backup-bucket-creds-' + slug,
bucketNamePrefix: ob.name_prefix + slug + '-',
encryptionSecretNameLabel: encryption.label,
region: s3.region,
endpoint: endpointResolved,
backupSchedule: sched.backup,
checkSchedule: sched.check,
pruneSchedule: sched.prune,
retention: std.manifestJsonEx(sched.retention, ' '),
};

local managedResource = {
apiVersion: 'espejote.io/v1alpha1',
kind: 'ManagedResource',
metadata: {
name: objectPrefix,
namespace: nsName,
},
spec: {
serviceAccountRef: { name: saName },
context: [
{
name: 'backup_namespaces',
resource: {
apiVersion: 'v1',
kind: 'Namespace',
namespace: '',
Comment thread
martini-source marked this conversation as resolved.
labelSelector: {
matchLabels: {
[backupNs.label]: backupNs.value,
},
},
},
},
],
triggers: [
{ name: 'namespace', watchContextResource: { name: 'backup_namespaces' } },
],
template: template,
},
};

// Define outputs below
{
[if params.createNamespace then 'namespace']: namespace,
serviceAccount: serviceAccount,
clusterRole: clusterRole,
clusterRoleBinding: clusterRoleBinding,
managedResource: managedResource,
}
17 changes: 16 additions & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
":disableDependencyDashboard"
],
"ignorePaths": [
".github/**"
".github/workflows/auto-release.yaml",
".github/workflows/release.yaml",
".github/workflows/test.yaml"
],
"labels": [
"dependency"
Expand All @@ -23,6 +25,19 @@
"suppressNotifications": [
"artifactErrors"
],
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"^\\.github/workflows/espejote\\.ya?ml$"
],
"matchStrings": [
"ESPEJOTE_VERSION:\\s*(?<currentValue>v?[0-9.]+)"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "vshn/espejote"
}
],
"packageRules": [
{
"matchUpdateTypes": [
Expand Down
4 changes: 4 additions & 0 deletions tests/golden/defaults/defaults/apps/defaults.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spec:
syncPolicy:
syncOptions:
- ServerSideApply=true
Loading
Loading