Parsing Kubernetes ConfigMap in Mixed Data Formats - json

Consider this current namespace config in JSON format:
$ kubectl get configmap config -n metallb-system -o json
{
"apiVersion": "v1",
"data": {
"config": "address-pools:\n- name: default\n protocol: layer2\n addresses:\n - 192.168.0.105-192.168.0.105\n - 192.168.0.110-192.168.0.111\n"
},
"kind": "ConfigMap",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"config\":\"address-pools:\\n- name: default\\n protocol: layer2\\n addresses:\\n - 192.168.0.105-192.168.0.105\\n - 192.168.0.110-192.168.0.111\\n\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"config\",\"namespace\":\"metallb-system\"}}\n"
},
"creationTimestamp": "2020-07-10T08:26:21Z",
"managedFields": [
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:data": {
".": {},
"f:config": {}
},
"f:metadata": {
"f:annotations": {
".": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {}
}
}
},
"manager": "kubectl",
"operation": "Update",
"time": "2020-07-10T08:26:21Z"
}
],
"name": "config",
"namespace": "metallb-system",
"resourceVersion": "2086",
"selfLink": "/api/v1/namespaces/metallb-system/configmaps/config",
"uid": "c2cfd2d2-866c-466e-aa2a-f3f7ef4837ed"
}
}
I am interested only in the address pools that are configured. As per the kubectl cheat sheet, I can do something like this to fetch the required address range:
$ kubectl get configmap config -n metallb-system -o jsonpath='{.data.config}'
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.0.105-192.168.0.105
- 192.168.0.110-192.168.0.111
However, my requirement is to use only a JSON parser throughout, and I cannot parse the above output since it is in YAML instead.
Since I'm not willing to accomodate the above yaml output for direct use ( or via format conversion operation ), is there any suitable way I can obtain the address range from the kubectl interface in a JSON format instead?

You need yq alongside kubectl ...
After inspecting your configmap, I understood the structure when I convert it to YAML :
---
apiVersion: v1
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.0.105-192.168.0.105
- 192.168.0.110-192.168.0.111
kind: ConfigMap
metadata:
name: config
And you can see clearly that .data.config is a multi-line string.. but it can be converted also to YAML.
parse the multi-line string with kubectl
treat this string as yaml using yq.
So This is what you are looking for :
# all addresses
kubectl -n metallb-system get cm config -o 'go-template={{index .data "config" }}' | \
yq -r '.["address-pools"][0].addresses'
# first address only
kubectl -n metallb-system get cm config -o 'go-template={{index .data "config" }}' | \
yq -r '.["address-pools"][0].addresses[0]'
# second address only
kubectl -n metallb-system get cm config -o 'go-template={{index .data "config" }}' | \
yq -r '.["address-pools"][0].addresses[1]'
# so on ...

Related

json_query in Ansible filtering output from nvme command returns nothing?

I'm having a hell of a time trying to use json_query to filter the JSON output of some external command (nvme list -o json).
The nvme output is captured in Ansible with this:
- name: Run nvme command
command:
cmd: nvme list -o json
register: nvme_output
become: yes
Contains:
- name: nvme_ouput stdout
debug:
var: nvme_output.stdout
TASK [general : nvme_ouput stdout] *********************************************
ok: [dev-0600099bfee3c0f60.internal] => {
"nvme_output.stdout": {
"Devices": [
{
"DevicePath": "/dev/nvme0n1",
"Firmware": "1.0",
"Index": 0,
"MaximumLBA": 33554432,
"ModelNumber": "Amazon Elastic Block Store",
"PhysicalSize": 17179869184,
"ProductName": "Non-Volatile memory controller: Vendor 0x1d0f Device 0x8061",
"SectorSize": 512,
"SerialNumber": "vol0e6af89fd1991cwtf",
"UsedBytes": 0
},
{
"DevicePath": "/dev/nvme1n1",
"Firmware": "1.0",
"Index": 1,
"MaximumLBA": 104857600,
"ModelNumber": "Amazon Elastic Block Store",
"PhysicalSize": 53687091200,
"ProductName": "Non-Volatile memory controller: Vendor 0x1d0f Device 0x8061",
"SectorSize": 512,
"SerialNumber": "vol0885625da4a60awtf",
"UsedBytes": 0
}
]
}
}
I now need all the DevicePaths. Using the online tester on eg. https://jmespath.org/, I came up with this rather simple query: Devices[*].DevicePath
Trying to use it in Ansible:
- name: nvme_ouput json_query var
debug:
msg: "{{ nvme_output.stdout | json_query(jq) }}"
vars:
jq: >-
Devices[*].DevicePath
But…
TASK [general : nvme_ouput json_query var] *********************
ok: [dev-0600099bfee3c0f60.internal] => {
"msg": ""
}
Why is the output of json_query() empty?
I'm running this on AWS EC2 on a Debian 10 system, if it matters, with:
ansible-playbook 2.7.7
config file = /home/alex/sysops-ansible/ansible.cfg
configured module search path = ['/home/alex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible-playbook
python version = 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0]

Can I set env key from param in template

in OpenShift 4.3, I'm trying to set env key from param value within a template. for example:
"env": [
{
"name: "${FOO}-TEST",
"value": "${BAR}"
},
{
"name: "TEST",
"value": "${BAR}"
}
]
"parameters": [
{
"name": "FOO",
"required": true
},
{
"name": "BAR",
"required": true
}
]
Then, oc new-app with -p FOO=X -p BAR=Y, and checking env vars on pod, it shows:
TEST=Y
But does not show:
X-TEST=Y
In template, can I not include a parameter value as env key?
I think you can set up a parameter value as env key.
Could you check the template is working well as you expected as follows ?
export the template as yaml file first.
$ oc get template <your template name> -o yaml > test-template.yml
check whether the parameter you specified is setting up or not from the output.
$ oc process -f test-template.yml -p FOO=X -p BAR=Y
It's my simple test result.
e.g.>
$ cat test-temp.yml
:
containers:
- env:
- name: "${NAME}-KEY"
value: ${NAME}
:
$ oc process -f test-temp.yml -p NAME=test
:
"containers": [
{
"env": [
{
"name": "test-KEY",
"value": "test"
}
],
:
I hope it help you.
Export your variables
oc process FOO=${FOO} BAR=${BAR} -f yamlFile

how to patch a route with an alternate backend

I have been trying to patch a route in openshift that has an alternate backend.
I have tried:
oc patch route/image-mirror-poc --patch '{"spec":{"alternateBackends": "kind:Service" "name:image-mirror-poc-blue" "weight:75"}}'
Error: Error from server: invalid character '"' after object key:value pair
oc patch route/image-mirror-poc --patch '{"spec":{"alternateBackends": "kind:Service", "name:image-mirror-poc-blue", "weight:75"}}'
Error: Error from server: invalid character ',' after object key
oc patch route/image-mirror-poc --patch '{"spec":{"alternateBackends": ["kind:Service", "name:image-mirror-poc-blue", "weight:75"]}}'
Error: Error from server: cannot restore struct from: string
I pulled the current spec of my route and it looks like:
"spec": {
"alternateBackends": [
{
"kind": "Service",
"name": "image-mirror-poc-blue",
"weight": 75
}
],
"host": "image-mirror-poc.sbx1apps.ocp.delta.com",
"port": {
"targetPort": "8080-tcp"
},
"to": {
"kind": "Service",
"name": "image-mirror-poc-green",
"weight": 25
},
"wildcardPolicy": "None"
}
I have been trying to following the documentation but unsuccessful.
Openshift info:
$ oc version
oc v3.9.0+191fece
kubernetes v1.9.1+a0ce1bc657
features: Basic-Auth
Server https://mycluster.ocp.mycompany.com
openshift v3.7.23
kubernetes v1.7.6+a08f5eeb6
How about this format ? If the changes are nothing, then the patch command finish with no changes.
oc patch route/image-mirror-poc --patch'{"spec": {"alternateBackends": [{"kind": "Service","name": "image-mirror-poc-blue","weight": 75}]}}'

How to change a ConfigMap of a Deployment using a script?

There are Deployments that may use a configmap with the name like cm-myapp-*. How to write a script that looks at all Deployments and reconfigures them from using some of their cm-myapp-* to the new specific cm-myapp-123?
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: myapp
spec:
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:2
volumeMounts:
- name: config-volume
mountPath: /etc/myapp/
volumes:
- name: config-volume
configMap:
name: cm-myapp-9375546193
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-myapp-123
data:
myapp.conf: |
hi
There is kubectl patch that accepts 'JSON patches', and there is kubectl edit that looks like interactive-only. Some kubectl commands accept go-templates, but they aren't for editing. Dumping the whole config gives some superfluous fields.
Can extract some stuff:
kubectl get deployment -o go-template --template="{{range .items}}{{\$deploymentName := .metadata.name}}{{range .spec.template.spec.volumes}}{{if .configMap}}{{\$deploymentName}} {{.configMap}}:{{end}}{{end}}{{end}}" | tr ':' '\n'
kubectl get deployment myapp -ojsonpath="{.spec.template.spec.volumes[0].configMap.name}}"
Need to patch it (not working):
kubectl patch deployment myapp -p '{ "op": "replace", "path": ".spec.template.spec.volumes[0].name", "value": "cf" }'
So how can it be done? What's the syntax of kubectl patch?
Use jq, the "awk for json" to tranform the JSON document(s). I'm not sure which fields you want to change exactly, but how to adjust it should be clear from the jq argument.
$ cat x.json
{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"foo": "myapp"
"metadata": {
"name": "myapp"
},
"spec": {
"template": {
"metadata": {
"labels": {
"app": "myapp"
}
}
}
}
}
$ jq '
.metadata.name = "cm-myapp-123"
| .spec.template.metadata.labels.app = "cm-myapp-123"
| .
' < x.json
{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"foo": "myapp"
"metadata": {
"name": "cm-myapp-123"
},
"spec": {
"template": {
"metadata": {
"labels": {
"app": "cm-myapp-123"
}
}
}
}
}
In my opinion it would be optimal if you knew the deployments in advance. You should generate your manifests that you apply from some templating solution (I would suggest getting familiar with helm which is much more then just templates) and then have the configmap managed with the templating.
Outputs an old config name, its index in the 'volumes' array and the name of the deployment. Filters out configs that we aren't interested in, patches all deployments.
#!/bin/bash
name=cm-myapp
unique_name=cm-myapp-123
# Columns: ConfigMap name, index in volumes, Deployment name.
kubectl get deployment -o go-template --template="{{range .items}}{{\$deploymentName := .metadata.name}}{{range \$i, \$v := .spec.template.spec.volumes}}{{if .configMap}}{{.configMap.name}} {{\$i}} {{\$deploymentName}}:{{end}}{{end}}{{end}}" | tr ':' '\n' |
egrep "^$name-[^-]+ " | while read l; do
i=$(printf '%s\n' "$l" | awk '{print $2}')
deployment=$(printf '%s\n' "$l" | awk '{print $3}')
kubectl patch deployment $deployment --type=json -p "[{ \"op\": \"replace\", \"path\": \"/spec/template/spec/volumes/$i/configMap/name\", \"value\": \"$unique_name\" }]"
done
Here's some kubectl patch syntax I've used to patch images by container name:
-p "{\"spec\":{\"template\":{\"spec\":{\"volumes\":[{\"name\":\"myapp\",\"image\":\"$imageUri\"}]}}}}"
The same thing might work for you by patching the volumes key instead:
-p "{\"spec\":{\"template\":{\"spec\":{\"volumes\":[{\"name\":\"config-volume\",\"configMap\":{\"name\":\"myapp-123\"}}]}}}}"
What's the syntax of kubectl patch?
The official documentation is here with examples here. According to that guide, you might try setting --type=json on your patch command.
There are two syntax: JSON Patch and JSON Merge Patch.

Ansible | Process data which can be either JSON or YAML

I'm using Ansible to read a config, which can be either JSON or YAML and extract values from some of the nodes in the file.
I know I can use from_json or from_yaml to process it in Ansible, but since I don't know which format the config will be in, I'm having difficulty making it work.
The file is Kubernetes' Kubeconfig. Examples below:
in YAML
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://my-k8s-cluster.com
name: k8s-clstr-master
contexts:
- context:
cluster: k8s-clstr-master
namespace: kube-system
user: k8s-clstr-master-admin
name: k8s-clstr-master
current-context: k8s-clstr-master
kind: Config
preferences: {}
users:
- name: k8s-clstr-master-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
in JSON
{
"kind": "Config",
"apiVersion": "v1",
"preferences": {},
"clusters": [
{
"name": "k8s-clstr-master",
"cluster": {
"server": "https://my-k8s-cluster.com",
"certificate-authority-data": "REDACTED"
}
}
],
"users": [
{
"name": "k8s-clstr-master-admin",
"user": {
"client-certificate-data": "REDACTED",
"client-key-data": "REDACTED"
}
}
],
"contexts": [
{
"name": "k8s-clstr-master",
"context": {
"cluster": "k8s-clstr-master",
"user": "k8s-clstr-master-admin",
"namespace": "kube-system"
}
}
],
"current-context": "k8s-clstr-master"
}
Ansible I'm using:
vars:
kubeconfig: "{{ lookup('hashivault', '/kubeconfig/admin', 'config') }}"
tasks:
- name: Find cluster server name
shell: "echo {{ kubeconfig.clusters[0].cluster.server }}"
Above Ansible block will work okay if kubeconfig is retrieved in JSON format, but it will fail if it's retrieved as in YAML format.
I might be able to make a task with |from yaml and then add ignore_errors: true, but that just doesn't feel like right way of doing it.
Anyone has any tips for me on how I can approach this problem?
There are some built-in tests in Jinja2.
The way Ansible templator works if you have JSON string inside {{...}} expression, it is automatically converted to object. So if you fetch JSON from your vault, kubeconfig becomes object, otherwise it is a string.
Here's a recipe for you:
vars:
kubeconfig_raw: "{{ lookup('hashivault', '/kubeconfig/admin', 'config') }}"
kubeconfig: "{{ kubeconfig_raw if kubeconfig_raw is mapping else kubeconfig_raw | from_yaml }}"
tasks:
- name: Find cluster server name
shell: "echo {{ kubeconfig.clusters[0].cluster.server }}"
If you use the include_vars task, it does not matter which format you provide. The task accepts both.
---
- hosts: localhost
connection: local
tasks:
- include_vars:
file: config
name: kubeconfig
- debug: var=kubeconfig