Add tekton pipelines

This commit is contained in:
Nikolai Rodionov 2024-04-02 19:15:40 +02:00
parent 217a105a5c
commit e97a9f7f24
No known key found for this signature in database
GPG Key ID: 0AA46A90E25592AD
23 changed files with 711 additions and 76 deletions

View File

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -0,0 +1,24 @@
apiVersion: v2
name: tekton-pipelines
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

View File

@ -0,0 +1 @@
TODO: Write some notes

View File

@ -0,0 +1,51 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "tekton-pipelines.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "tekton-pipelines.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "tekton-pipelines.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "tekton-pipelines.labels" -}}
helm.sh/chart: {{ include "tekton-pipelines.chart" . }}
{{ include "tekton-pipelines.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "tekton-pipelines.selectorLabels" -}}
app.kubernetes.io/name: {{ include "tekton-pipelines.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -0,0 +1,30 @@
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: hetzner-cleanup
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: environment
type: string
- name: namespace
type: string
tasks:
- name: cleanup-hetzner-infra
retries: 3
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: cleanup-hetzner-infra
- name: namespace
value: tekton-pipelines
params:
- name: environment
value: $(params.environment)
- name: namespace
value: $(params.namespace)

View File

@ -0,0 +1,99 @@
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: hetzner-k3s
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: environment
type: string
- name: namespace
type: string
workspaces:
- name: ssh-keys
- name: inventory
- name: kubeconfig-output
tasks:
- name: generate-ssh-keys
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: generate-ssh-keys
- name: namespace
value: tekton-pipelines
workspaces:
- name: ssh-keys
workspace: ssh-keys
params:
- name: environment
value: $(params.environment)
- name: namespace
value: $(params.namespace)
- name: prepare-hetzner-infra
retries: 3
runAfter:
- generate-ssh-keys
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: prepare-hetzner-infra
- name: namespace
value: tekton-pipelines
workspaces:
- name: outputs
workspace: inventory
params:
- name: environment
value: $(params.environment)
- name: namespace
value: $(params.namespace)
- name: bootstrap-k3s
retries: 3
runAfter:
- prepare-hetzner-infra
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: bootstrap-k3s
- name: namespace
value: tekton-pipelines
params:
- name: environment
value: $(params.environment)
- name: namespace
value: $(params.namespace)
workspaces:
- name: outputs
workspace: kubeconfig-output
- name: deploy-helmfile-base
runAfter:
- bootstrap-k3s
retries: 3
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: deploy-helmfile-base
- name: namespace
value: tekton-pipelines
params:
- name: environment
value: $(params.environment)
- name: namespace
value: $(params.namespace)
workspaces:
- name: outputs
workspace: kubeconfig-output

View File

@ -0,0 +1,36 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
namespace: {{ .Values.pipelineRunNamespace }}
name: secret-manager
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["*"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: secret-manager
namespace: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: secret-manager
namespace: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: secret-manager
namespace: {{ .Values.pipelineRunNamespace }}
roleRef:
kind: ClusterRole
name: secret-manager
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}

View File

@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: shell-operator
namespace: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "tekton-pipelines.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "tekton-pipelines.labels" . | nindent 8 }}
spec:
serviceAccountName: shell-operator
containers:
- name: shell-operator
env:
- name: SP_TEKTON_RUNTIME_NS
value: {{ .Values.pipelineRunNamespace }}
imagePullPolicy: Always
image: git.badhouseplants.net/softplayer/softplayer-controller:latest

View File

@ -0,0 +1,39 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
namespace: {{ .Values.pipelineRunNamespace }}
name: shell-operator
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["*"]
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns"]
verbs: ["*"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: shell-operator
namespace: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: shell-operator
namespace: {{ .Values.pipelineRunNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: shell-operator
namespace: {{ .Values.pipelineRunNamespace }}
roleRef:
kind: ClusterRole
name: shell-operator
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,97 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: bootstrap-k3s
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: namespace
type: string
- name: environment
type: string
steps:
- name: get-ssh-key-and-inventory
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl get secret "$(params.environment)"-ssh \
--namespace "$(params.namespace)" -o yaml \
| yq '.data.ssh_key' \
| base64 -d > /tmp/outputs/ssh_key
kubectl get secret "$(params.environment)"-ssh \
--namespace "$(params.namespace)" -o yaml \
| yq '.data."ssh_key.pub"' \
| base64 -d > /tmp/outputs/ssh_key.pub
kubectl get secret "$(params.environment)"-inventory \
--namespace "$(params.namespace)" -o yaml \
| yq '.data."inventory.yaml"' \
| base64 -d > /tmp/outputs/inventory.yaml
chmod 0600 /tmp/outputs/ssh_key
chmod 0600 /tmp/outputs/ssh_key.pub
- name: prepare-servers
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
env:
- name: SP_ENV
value: $(params.environment)
- name: SP_CUSTOMER
value: $(params.namespace)
- name: ANSIBLE_INVENTORY
value: /tmp/outputs/inventory.yaml
- name: ANSIBE_PRIVATE_KEY_FILE
value: /tmp/outputs/ssh_key
- name: ANSIBLE_HOST_KEY_CHECKING
value: "false"
script: |
#!/bin/sh
ansible-playbook /src/playbooks/systems/system-bootstrap/playbook.yml
- name: prepare-k3s
env:
- name: SP_ENV
value: $(params.environment)
- name: SP_CUSTOMER
value: $(params.namespace)
- name: ANSIBLE_INVENTORY
value: /tmp/outputs/inventory.yaml
- name: ANSIBE_PRIVATE_KEY_FILE
value: /tmp/outputs/ssh_key
- name: ANSIBLE_HOST_KEY_CHECKING
value: "false"
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
script: |-
#!/bin/sh
ansible-playbook /src/playbooks/systems/k3s-bootstrap/playbook.yml
- name: prepare-kubeconfig
env:
- name: SP_ENV
value: $(params.environment)
- name: SP_CUSTOMER
value: $(params.namespace)
- name: ANSIBLE_INVENTORY
value: /tmp/outputs/inventory.yaml
- name: ANSIBE_PRIVATE_KEY_FILE
value: /tmp/outputs/ssh_key
- name: ANSIBLE_HOST_KEY_CHECKING
value: "false"
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
script: |-
#!/bin/sh
ansible-playbook /src/playbooks/other/k8s-create-user/playbook.yml
- name: save-kubeconfig
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl delete secret \
"$(params.environment)"-config \
--namespace "$(params.namespace)"
kubectl create secret generic \
"$(params.environment)"-config \
--namespace "$(params.namespace)" \
--from-file=kubeconfig=/tmp/outputs/admin-default-config
workspaces:
- name: outputs
description: A folder to store outputs
optional: false
mountPath: /tmp/outputs

View File

@ -0,0 +1,44 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: cleanup-hetzner-infra
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: namespace
type: string
- name: environment
type: string
steps:
- name: create-hetzner-infra
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
env:
- name: SP_STATE
value: absent
- name: SP_ENV
value: $(params.environment)
- name: SP_CUSTOMER
value: $(params.namespace)
- name: SOPS_AGE_KEY
value: AGE-SECRET-KEY-1VXYUK7MAGR6KMZJ6ZMPD35EQ9LVXXKQ2HHE6Z8T828WWT03EH8LS6G9AS8
script: |-
#!/bin/sh
mkdir -p /tmp/outputs
ssh-keygen -t rsa -f /tmp/outputs/ssh_key -N ""
ansible-playbook /src/playbooks/providers/hetzner/playbook.yml || true
- name: remove-secrets
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl delete secret \
"$(params.environment)-inventory" \
--namespace "$(params.namespace)"
kubectl delete secret \
"$(params.environment)-ssh" \
--namespace "$(params.namespace)"
kubectl delete secret \
"$(params.environment)-config" \
--namespace "$(params.namespace)"

View File

@ -0,0 +1,39 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: deploy-helmfile-base
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: namespace
type: string
- name: environment
type: string
steps:
- name: get-ssh-key
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl get secret "$(params.environment)"-config \
--namespace "$(params.namespace)" -o yaml \
| yq '.data.kubeconfig' \
| base64 -d > /tmp/outputs/config
chmod 0600 /tmp/outputs/config
- name: deploy-helmfile
image: ghcr.io/helmfile/helmfile:v0.163.1
env:
- name: KUBECONFIG
value: /tmp/outputs/config
script: |-
#!/bin/sh
mkdir -p /src
git clone https://git.badhouseplants.net/softplayer/softplayer-helmfile.git /src/helmfile
cd /src/helmfile/workload && helmfile sync
workspaces:
- name: outputs
description: A folder to store outputs
optional: false
mountPath: /tmp/outputs

View File

@ -0,0 +1,49 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: generate-ssh-keys
namespace: tekton-pipelines
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
description: |-
This task should prepare a ssh key that will be used for
bootstrapping wotkload nodes. If ssh-key secret already
exists, should not run
params:
- name: namespace
type: string
- name: environment
type: string
steps:
- name: check-whether-a-key-exists
image: alpine/k8s:1.29.2
script: |-
#!/bin/bash
if kubectl get secret "$(params.environment)-ssh"; then
echo 1 > /tmp/outputs/ready
fi
- name: prepare-ssh-key
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
script: |-
#!/bin/bash
if ! [ -f /tmp/outputs/ready ]; then
ansible-playbook /src/playbooks/other/ssh-key-gen/playbook.yml
fi
- name: save-ssh-keys
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
if ! [ -f /tmp/outputs/ready ]; then
kubectl create secret generic \
"$(params.environment)"-ssh \
--namespace "$(params.namespace)" \
--from-file /tmp/outputs
fi
workspaces:
- name: ssh-keys
description: A folder to store ssh keys
optional: false
mountPath: /tmp/outputs

View File

@ -0,0 +1,54 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: prepare-hetzner-infra
namespace: {{ .Values.pipelineNamespace }}
labels:
{{- include "tekton-pipelines.labels" . | nindent 4 }}
spec:
params:
- name: namespace
type: string
- name: environment
type: string
steps:
- name: get-ssh-key
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl get secret "$(params.environment)"-ssh \
--namespace "$(params.namespace)" -o yaml \
| yq '.data."ssh_key.pub"' \
| base64 -d > /tmp/outputs/ssh_key.pub
chmod 0600 /tmp/outputs/ssh_key.pub
- name: create-hetzner-infra
image: git.badhouseplants.net/softplayer/softplayer-coskgne:latest
env:
- name: SP_STATE
value: present
- name: SP_ENV
value: $(params.environment)
- name: SP_CUSTOMER
value: $(params.namespace)
- name: SOPS_AGE_KEY
value: {{ .Values.providers.hetzner.ageKey }}
script: |-
#!/bin/sh
ansible-playbook /src/playbooks/providers/hetzner/playbook.yml
- name: save-inventory
image: alpine/k8s:1.29.2
script: |-
#!/bin/sh
kubectl delete secret \
"$(params.environment)"-inventory \
--namespace "$(params.namespace)"
kubectl create secret generic \
"$(params.environment)"-inventory \
--namespace "$(params.namespace)" \
--from-file /tmp/outputs/inventory.yaml
workspaces:
- name: outputs
description: A folder to store outputs
optional: false
mountPath: /tmp/outputs

View File

@ -0,0 +1,8 @@
#U+00eU+002 U+002Default values for tekton-pipelines.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
providers:
hetzner:
ageKey: test
pipelineNamespace: tekton-pipelines
pipelineRunNamespace: tekton-runtime

5
service/.sops.yaml Normal file
View File

@ -0,0 +1,5 @@
creation_rules:
- path_regex: .*
key_groups:
- age:
- age1mrdee45qq36trja45u0wcem7c2mgydw35zkuhh97khgc7veanaaq29wzh4

View File

@ -54,6 +54,15 @@ releases:
- kube-system/cilium
- kube-system/namespaces
- name: tekton-pipelines
namespace: tekton-system
createNamespace: false
chart: ../charts/tekton-pipelines
secrets:
- ./secrets/pipelines.yaml
needs:
- tekton-system/tekton
- name: cert-manager
chart: zot/cert-manager
version: v1.14.4

View File

@ -0,0 +1,23 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: hetzner-cleanup
namespace: default
spec:
params:
- name: namespace
value: default
- name: environment
value: default
- name: customer
value: allanger
pipelineRef:
resolver: cluster
params:
- name: kind
value: pipeline
- name: name
value: hetzner-cleanup
- name: namespace
value: tekton-pipelines
serviceAccountName: secret-manager

View File

@ -1,8 +1,30 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: hetzner-k3s
generateName: hetzner-k3s
namespace: tekton-runtime
spec:
params:
- name: namespace
value: default
- name: environment
value: default
- name: customer
value: allanger
pipelineRef:
name: hetzner-k3s
serviceAccountName: default
resolver: cluster
params:
- name: kind
value: pipeline
- name: name
value: hetzner-k3s
- name: namespace
value: tekton-pipelines
workspaces:
- name: ssh-keys
emptyDir: {}
- name: inventory
emptyDir: {}
- name: kubeconfig-output
emptyDir: {}
serviceAccountName: secret-manager

View File

@ -1,15 +0,0 @@
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: hetzner-k3s
spec:
tasks:
- name: generate-ssh-keys
taskRef:
name: generate-ssh-keys
- name: prepare-hetzner-infra
taskRef:
name: prepare-hetzner-infra
- name: bootstrap-k3s
taskRef:
name: bootstrap-k3s

View File

@ -1,58 +0,0 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: generate-ssh-keys
spec:
steps:
- name: prepare-ssh-key
image: alpine
imagePullPolicy: Never
script: |-
#!/bin/sh
echo "Generate SSH keys"
- name: save-ssh-keys
image: alpine
script: |-
#!/bin/sh
echo "Save public and private keys to k8s secret"
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: prepare-hetzner-infra
spec:
steps:
- name: create-hetzner-infra
image: alpine
imagePullPolicy: Never
script: |-
#!/bin/sh
echo "Create hetzner server and everything else"
- name: save-inventory
image: alpine
script: |-
#!/bin/sh
echo "Inventory file that is generated by ansible, must be saved to secrets"
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: bootstrap-k3s
spec:
steps:
- name: prepare-servers
image: alpine
script: |
#!/bin/sh
echo "Prepere nodes"
- name: prepare-k3s
image: alpine
script: |-
#!/bin/sh
echo "Bootsrap k3s"
- name: save-kubeconfig
image: alpine
script: |-
#!/bin/sh
echo "check if kubeconfig is valid and save it to k8s secrets"

View File

@ -0,0 +1,23 @@
providers:
hetzner:
ageKey: ENC[AES256_GCM,data:xF5+jYQFI/F+o5t481gCKkh9e8I9oaZkBC28GjskAvpnda6EpknF9PgMHt3mmw7JPRU72ZRK+q7HVuILugZ+VEa0GGHNnGUO1t4=,iv:SUcXsEX4a766C/hkObRPHxRTKv0Ul+8uiu9Q/XrWKlA=,tag:pGTJbsicPXIb2ik8LTjnNg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1mrdee45qq36trja45u0wcem7c2mgydw35zkuhh97khgc7veanaaq29wzh4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzTXVzeE8rUEVEcWE0OWwr
cjA1aVgxSDAwVjBIUUVpOHBGcjRTOEVkSWp3CjhERWlLOUplNGxMRCtEMVMxTXls
MGZQS0x6YXdiOG1XNTBGcVFoMEdodU0KLS0tIFZPc0FMbVpTZnZsMnhvdVY3Q1Va
b3cyZWV1dW5RakFseUxNdXZqaEtsVFUK0PJqIDXM7eBFN+mZ2FG8mEwajBzuGU1Y
iqC+5EMj3R2v+Dt+5P+dS/loYFo92YELyZgveFVzrgOArOKoEslrTQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-04-02T14:59:34Z"
mac: ENC[AES256_GCM,data:UL2PH2AmN3r2Z3hTm7lLDgd/+xte/DMgAXiJwl4gcfCBzdA0ThMvVsCBMjfuxwTTEKuJEBR4V2l68//wC0zgcV6IrjvNXrlP8nB1TsKIkEh+o9nF27zp6mLIBf5QP5BDrh0uKbj9gEsodHrAfjeaoNh9DhjgXHuabCLe1EVyxCI=,iv:xhjsriHCvDBa1iJRknImGtqIeEy/nepQdshAB4OKaVg=,tag:KqXlg9x0ajihelpnfrHk8g==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1