Variable substitution in a configuration file - kubernetes

Variable substitution in Gitlab only seem to work inside the gitlab-ci.yml file.
However I have a configuration file (k8s secret) where I would like to set different values depending if I'm in a staging or a production environment.
apiVersion: v1
kind: Secret
metadata:
name: tls-config
namespace: myNamespace
type: kubernetes.io/tls
data:
tls.crt: |
${TLS_CRT}
tls.key: |
${TLS_KEY}
Where TLS_CRT & TLS_KEY would be variables defined in /settings/ci_cd/variables.
How should one handle variable substitution in this case?

You could use an editor like sed
For example, if you had the file like:
data:
tls.crt: |
#TLS_CRT#
tls.key: |
#TLS_KEY#
You could use sed like this as part of your GitLab job to replace its contents:
myjob:
script:
- sed -i "s|#TLS_CRT#|${TLS_CRT}|" ./path/to/file
- sed -i "s|#TLS_KEY#|${TLS_KEY}|" ./path/to/file
Then the file contents will be something like this:
data:
tls.crt: |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key: |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
Using the pattern #word# is not totally necessary. However, it does make using the sed easier, as it avoids needing to escape the literal ${} characters in the shell command.
As a short explanation of the sed command used here:
# v shorthand for --in-place; edits the file directly
sed -i "s|FIRST_ARGUMENT|SECOND_ARGUMENT|" ./path/to/file
# ^ replace this ^ with this ^ in this file
Fore more info, see here: https://unix.stackexchange.com/a/112024/453397

Related

Use external variable value in gitlab ci yaml

I need to deploy same application on different namespaces in Kubernetes cluster. I have a template manifest file app-template.yml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-env
data:
BASE_PATH: "https://{DOMAIN}/{NAME}/app/"
NON_PROXY_DOMAINS: "{DOMAIN}/{NAME}"
PROXY_SERVER: "{DOMAIN}"
In a bash I would usually do something like replace those with variables I define and create new manifest file and apply it in a namespace, something like:
export NAMESPACE="mynamespace"
export DOMAIN="mydomain"
export NAME="myname"
sed "s/{DOMAIN}/${DOMAIN}/g; s/{NAME}/${NAME}/g;" app-template.yml | tee app-${NAMESPACE}.yml
kubectl apply -f app-${NAMESPACE}.yml --namespace $NAMESPACE
I would like to do that in gitlab ci way. I have web app which is going to provide those 3 variables:
const varParams = {
token: TOKEN,
“variables[namespace]“: namespace,
“variables[domain]“: url,
“variables[name]“: shortnerUrl,
};
I would like to pass those variables to my template file. Tried this:
stages:
- test
variables:
DOMAIN: <>
NAME: <>
NAMESPACE: <>
deploy:
stage: test
image: devth/helm:latest
environment: dev
script:
- echo "${NAMESPACE}"
- # copy secret from default namespace
- "kubectl get secret pull-secret --namespace=app-main -o yaml | sed 's/namespace: .*/namespace: ${NAMESPACE}/' | kubectl apply -f -"
- "sed 's/{DOMAIN}/${DOMAIN}/g; s/{NAME}/${NAME}/g;' app-template.yml | tee app-${NAMESPACE}.yml"
I'm currently running manual pipeline and defining all 3 variables, but is there another, more "gitlab way" to do it instead of replacing the values with sed and creating new files and applying them with kubectl?

How to create a Kubernetes configMap from part of a yaml file?

As I know the way to create a configMap in Kubernetes from a file is to use:
--from-file option for kubectl
What I am looking for is a way to only load part of the yaml file into the configMap.
Example:
Let's say I have this yml file:
family:
Boys:
- name: Joe
- name: Bob
- name: dan
Girls:
- name: Alice
- name: Jane
Now I want to create a configMap called 'boys' which will include only the 'Boys' section.
Possible?
Another thing that could help if the above is not possible is when I am exporting the configMap as environment variables to a pod (using envFrom) to be able to only export part of the configMap.
Both options will work for me.
Any idea?
The ConfigMap uses a key and value for its configuration. Based on your example, you get multiple arrays of data where there are multiple values with their own keys. But you can create multiple ConfigMap from different file for these issues.
First you need to create .yaml files to create a ConfigMap guided by the documentation.
First file call Boys.yaml
# Env-files contain a list of environment variables.
# These syntax rules apply:
# Each line in an env file has to be in VAR=VAL format.
# Lines beginning with # (i.e. comments) are ignored.
# Blank lines are ignored.
# There is no special handling of quotation marks (i.e. they will be part of the ConfigMap value)).
name=Joe
name=Bob
name=Dan
Second file call Girls.yaml
name=Alice
name=Jane
Create your ConfigMap
kubectl create configmap NmaeOfYourConfigmap --from-env-file=PathToYourFile/Boys.yaml
where the output is similar to this:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp:
name: NmaeOfYourConfigmap
namespace: default
resourceVersion:
uid:
data:
name: Joe
name: Bob
name: Dan
Finally, you can pass these ConfigMap to pod or deployment using configMapRef entries:
envFrom:
- configMapRef:
name: NmaeOfYourConfigmap-Boys
- configMapRef:
name: NmaeOfYourConfigmap-Girls
Configmaps cannot contain rich yaml data. Only key value pairs. So if you want to have a list of things, you need to express this as a multiline string.
With that in mind you could use certain tools, such a yq to query your input file and select the part you want.
For example:
podman run -rm --interactive bluebrown/tpl '{{ .family.Boys | toYaml }}' < fam.yaml \
| kubectl create configmap boys --from-file=Boys=/dev/stdin
The result looks like this
apiVersion: v1
kind: ConfigMap
metadata:
name: boys
namespace: sandbox
data:
Boys: |+
- name: Joe
- name: Bob
- name: dan
You could also encode the file or part of the file with base64 and use that as an environment variable, since you get a single string, which is easily processable, out of it. For example:
$ podman run --rm --interactive bluebrown/tpl \
'{{ .family.Boys | toYaml | b64enc }}' < fam.yaml
# use this string as env variable and decode it in your app
LSBuYW1lOiBKb2UKLSBuYW1lOiBCb2IKLSBuYW1lOiBkYW4K
Or with set env which you could further combine with dry run if required.
podman run --rm --interactive bluebrown/tpl \
'YAML_BOYS={{ .family.Boys | toYaml | b64enc }}' < fam.yaml \
| kubectl set env -e - deploy/myapp
Another thing is, that YAML is a superset of JSON, in many cases you are able to convert YAML to JSON or at least use JSON like syntax.
This can be useful in such a scenario in order to express this as a single line string rather than having to use multiline syntax. It's less fragile.
Every YAML parser will be able to parse JSON just fine. So if you are parsing the string in your app, you won't have problems.
$ podman run --rm --interactive bluebrown/tpl '{{ .family.Boys | toJson }}' < fam.yaml
[{"name":"Joe"},{"name":"Bob"},{"name":"dan"}]
Disclaimer, I created the above used tool tpl. As mentioned, you might as well use alternative tools such as yq.

sed capture group with whitespaces does not work

I want to update a tag in a kubernetes kustomization.yaml with sed. the original file looks like this:
resources:
- ../../base
namePrefix: prod-
commonLabels:
env: production
images:
- name: my-service
newTag: current-version
patchesStrategicMerge:
- deployment.yaml
when i use my sed command it just does not work and i'm not sure why:
sed -r 'name: my-service\s*(newTag:\s*).*/\1new-version/g' overlays/production/kustomization.yaml
as far as i understand this it should match the newTag key if it follows a name: my-service element. I dont get any errors, it just does not work.
I'm currently testing this on MacOS
As RavinderSingh13 comments, yq will be an appropriate tool to handle
yaml file. If yq is available, would you please try:
yq -y '(.images[] | select(.name == "my-service") | .newTag) |= "new-version"' yourfile.yaml
Output:
resources:
- ../../base
namePrefix: prod-
commonLabels:
env: production
images:
- name: my-service
newTag: new-version
patchesStrategicMerge:
- deployment.yaml
If yq is not available and you have a specific reason to use sed, then try the alternative:
sed -E '
/my-service/{ ;# if the line matches "my-service", then execute the block
N ;# append the next line to the pattern space
s/(newTag:[[:space:]]*).*/\1new-version/ ;# replace the value
} ;# end of the block
' yourfile.yaml
The reason why your sed command does not work is because sed is a
line-oriented tool and process the input line by line. Your regex crosses
the lines and will not match.

Kubernetes Daemonset HELM charts - using command argument to set environment variable using cURL

I'm trying to pass in a command argument in Kubernetes Daemonset with Helm Charts which performs export of an new environment variable passed in from a cURL result.
command: ["/bin/bash","-c","export MACHINE_TYPE=$(curl --unix-socket /var/run/docker.sock http://docker/containers/rancher-agent/json | grep -oP 'CATTLE_HOST_LABELS=.+?\w+' | awk -F '=' '{print $2}')"]
The result should be that the variable is set in the container e.g. MACHINE_TYPE=compute
I have also tried using command + args like so:
command: ["/bin/bash","-c"]
args: ["export MACHINE_TYPE=$(`curl --unix-socket /var/run/docker.sock http://docker/containers/rancher-agent/json | grep -oP 'CATTLE_HOST_LABELS=.+?\w+' | awk -F = '{print $2}'`)"]
When I try to deploy the daemonset, I get an error message
"Error: YAML parse error on /templates/daemonset.yaml: error converting YAML to JSON: yaml: line 46: found unknown escape character"
The export command works if I run it from within the container.
My aim is to be able to set a final container environment variable (LABEL) from the daemonset.yaml which is concatenate of two environment variables e.g.
containers:
- name: {{ .Chart.Name }}
image: "{{.Values.image.repository}}:{{.Values.image.tag}}"
imagePullPolicy: {{.Values.image.pullPolicy}}
env:
- name: LABEL
value: $MACHINE_TYPE-$HOSTNAME
command: ["/bin/bash","-c"]
args: ["export MACHINE_TYPE=$(`curl --unix-socket /var/run/docker.sock http://docker/containers/rancher-agent/json | grep -oP 'CATTLE_HOST_LABELS=.+?\w+' | awk -F = '{print $2}'`)"]
so the 'env' output in the container for LABEL variable would be
LABEL=compute-ip-x-x-x-x.ap-southeast-2.compute.internal
I know that the value value: $MACHINE_TYPE-$HOSTNAME will not work, so hoping for assistance with that as well.
found unknown escape character 'w'
... rep -oP 'CATTLE_HOST_LABELS=.+?\w+' | awk -F '=' '{print $2}')"]
^)
The error message appears to be pretty straightforward: backslash is magic in a double-quoted string
If you use the more straightforward yaml structure, without using the double-quoted strings, you can use the single backslash as you wish:
command:
- /bin/bash
- -c
- export MACHINE_TYPE=$(curl --unix-socket /var/run/docker.sock http://docker/containers/rancher-agent/json | grep -oP 'CATTLE_HOST_LABELS=.+?\w+' | awk -F '=' '{print $2}')
and benefiting from the infinitely more legible syntax
Separately,
final container environment variable (LABEL) from the daemonset.yaml
In that case, you want the valueFrom: fieldPath: status.nodeName in your env: block to set HOSTNAME, and then (as you are currently doing) build up the LABEL from its MACHINE_TYPE component right before executing the actual in-container command. You cannot (that I am aware of) declare LABEL in the kubernetes descriptor's env block because you are mixing metaphors trying to run a command in a container that affects the kubernetes descriptor for that command.
I know that the value value: $MACHINE_TYPE-$HOSTNAME will not work, so hoping for assistance with that as well. Thanks in advance.
There are plenty of existing SO answers about that, but the syntax is $(MACHINE_TYPE)-$(HOSTNAME) assuming those two env-vars are declared in the Pod's env: block, and not otherwise

Automatically generated strings for secrets using yaml config

I have a deployment config for an app, that (among other things) creates a secret for a mysql database:
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_USER: my_user
MYSQL_PASSWORD: my_random_secret
MYSQL_DATABASE: my_db
MYSQL_ROOT_PASSWORD: my_random_secret
---
etc...
The deployment file is under source control, so I don't want to place the secrets there.
Does anyone know how I can tell Kubernetes to generate random strings for each variable which has my_random_secret as a value in my example? Preferably something that can be configured using the yaml file, without needing to invoke any extra commands.
As far I have understood that you do not want to keep your secret information locally. So that you need to generate them when you are creating that secret.
I think there is a way to create Kubernetes resource using go-template. Didn't find enough information for that. I can't help you in this way.
But you can also create secret using script. And your secret will not be exposed.
Following script can help you in that case. This will generate random password for you and will create secret with that.
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_PASSWORD: $(head -c 24 /dev/random | base64)
MYSQL_ROOT_PASSWORD: $(head -c 24 /dev/random | base64)
stringData:
MYSQL_USER: my_user
MYSQL_DATABASE: my_db
EOF
Run this script.
Hope it will work for you
If you are using Helm chart, you can do this:
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_USER: bXlfdXNlcgo=
MYSQL_PASSWORD: {{ randAlphaNum 16 | b64enc }}
MYSQL_DATABASE: bXlfZGIK
MYSQL_ROOT_PASSWORD: {{ randAlphaNum 16 | b64enc }}
Here,
echo "my_user" | base64 => bXlfdXNlcgo= &
echo "my_db" | base64 => bXlfZGIK
Otherwise, we can have a similar kind of feature. Or, if you want to generate it from the bash/shell script we can have $(head /dev/urandom | LC_ALL=C tr -dc A-Za-z0-9 | head -c16 | base64) as a unique password generator on the shell.
You can also use open ssl
openssl rand -base64 32
Or if you need plaintext/numbers:
openssl rand -base64 32 | tr -cd '[:alpha:]\n'
Or if you don't want the trailing CR:
openssl rand -base64 32 | tr -cd '[:alpha:]'
Note that anything longer than -base64 48 might add CRs to the output. Adjust your tr to taste, e.g.
openssl rand -base64 128 | tr -cd '[:alpha:]'
will concatenate the multiple lines from openssl, but omit a trailing \n as well