kustomize patching a specific container other than by array (/containers/0) - arrays

I'm trying to see if there's a way to apply a kustomize patchTransformer to a specific container in a pod other than using its array index. For example, if I have 3 containers in a pod, (0, 1, 2) and I want to patch container "1" I would normally do something like this:
patch: |-
- op: add
path: /spec/containers/1/command
value: ["sh", "-c", "tail -f /dev/null"]
That is heavily dependent on that container order remaining static. If container "1" is removed for whatever reason, the array is reshuffled and container "2" suddenly becomes container "1", making my patch no longer applicable.
Is there a way to patch by name, or target a label/annotation, or some other mechanism?
path: /spec/containers/${NAME_OF_CONTAINER}/command
Any insight is greatly appreciated.

For future readers: you may have seen JSONPath syntax like this floating around the internet, and hoped that you could select a list item and patch it using Kustomize.
/spec/containers[name=my-app]/command
As #Rico mentioned in his answer: This is a limitation with JSON6902 - it only accepts paths using JSONPointer syntax, defined by JSON6901.
So, no, you cannot currently address a list item using [key=value] syntax when using kustomize's patchesJson6902.
However, a solution to the problem that the original question highlights around potential reordering of list items does exist without moving to Strategic Merge Patch (which can depend on CRD authors correctly annotating how list-item merges should be applied).
Simply add another JSON6902 operation to your patches to test that the item remains at the index you specified.
# First, test that the item is still at the list index you expect
- op: test
path: /spec/containers/0/name
value: my-app
# Now that you know your item is still at index-0, it's safe to patch it's command
- op: replace
path: /spec/containers/0/command
value: ["sh", "-c", "tail -f /dev/null"]
The test operation will fail your patch if the value at the specified path does not match what is provided. This way, you can be sure that your other patch operation's dependency on the item's index is still valid!
I use this trick especially when dealing with custom resources, since I:
A) Don't have to give kustomize a whole new openAPI spec, and
B) Don't have to depend on the CRD authors having added the correct extension annotation (like: "x-kubernetes-patch-merge-key": "name") to make sure my strategic merge patches on list items work the way I need them to.

This is more of a Json6902 patch limitation together with the fact that containers are defined in a K8s pod as an Array and not a Hash where something like this would work:
path: /spec/containers/${NAME_OF_CONTAINER}/command
You could just try a StrategicMergePatch. which essentially what kubectl apply does.
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
selector:
matchLabels:
run: my-app
replicas: 2
template:
metadata:
labels:
run: my-app
spec:
containers:
- name: my-container
image: myimage
ports:
- containerPort: 80
EOF
cat <<EOF > set_command.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
template:
spec:
containers:
- name: my-app
command: ["sh", "-c", "tail -f /dev/null"]
EOF
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
patchesStrategicMerge:
- set_command.yaml
EOF
✌️

Related

ansible apt-key module with loop

I'm provisioning a system that requires multiple GPG keys to be added. I'm attempting to streamline the process and follow DRY principals.
I have apt packages installing from a vars list like so:
- name: Install packages
apt: name={{ apt_packages }}
Where my vars.yml looks like this:
apt_packages:
- tilix
- terraform
- ansible
- opera
This works because the apt module accepts comma separated inputs and parses accordingly.
So I'm trying to achieve a similar process when using the apt_key module but I can't seem to get it to work. Here are a couple of attempts I've made:
- name Add keys
apt_key:
url: url="{{ items }}"
loop: "{{ gpg_keys }}"
state: present
and
- name: Add GPG Keys
apt_key:
url: url="{{ gpg_keys }}"
state: present
Both throw different errors.
Is it possible to do something like this using the apt-key module? Obviously I'm trying to avoid having a separate caller for each key I want to add as there will be many keys and I'd like to be able to add additional keys later on by simply appending the list in vars.yml.
You have a few small mistakes in your task.
The right way is this:
- name: Add keys
apt_key:
url: "{{ item }}"
state: present
loop: "{{ gpg_keys }}"
you already have the key url, so prepending url= is incorrect
loop is an argument to the task and not to the apt_key module, so it needs to be indented to the level of apt_key (unlike url which is an argument to the model)
Sidenotes:
You also need to make sure that gpg_keys contains a list, similar to apt_packages.
The name parameter of apt accepts a list, as you define correctly in your vars.yml, no comma-separated string. (You are already doing it right)
Documentation:
apt
apt_key

How to keep latest version of cloudbuild.yaml seperate in the cloud storage

My cloudbuild.yaml consists of:
steps:
- name: maven:3.6.0-jdk-8-slim
entrypoint: 'mvn'
args: ["clean","install","-PgenericApiSuite","-pl", "api-testing", "-am", "-B"]
- name: 'gcr.io/cloud-builders/gsutil'
args: ['-m', 'cp', '-r', '/workspace/api-testing/target/cucumber-html-reports', 'gs://testing-reports/$BUILD_ID']
But every time it runs now my bucket shows the reports with its build_id.
Is there a way I can keep the latest report separate from the rest?
Sadly symbolic link doesn't exist in Cloud Storage. For achieving what you want, you have to handle this manually with these 2 steps at the end of your job:
# delete the previous existing latest directory
- name: 'gcr.io/cloud-builders/gsutil'
args: ['-m', 'rm', '-r', 'gs://testing-reports/latest']
# copy the most recent file into the latest directory
- name: 'gcr.io/cloud-builders/gsutil'
args: ['-m', 'cp', '-r', 'gs://testing-reports/$BUILD_ID', 'gs://testing-reports/latest']

How to loop an array of packages in an Ansible role

I have made role for installing php5-fpm (with other roles: nginx, worldpress, mysql). I want to install php5 set of packages, but have problem with the looping an array of packages. Please some tips how to solve this issue.
Role php5-fpm include:
roles/default/main.yml
roles/tasks/install.yml
default/main.yml:
---
# defaults file for php-fpm
# filename: roles/php5-fpm/defaults/main.yml
#
php5:
packages:
- php5-fpm
- php5-common
- php5-curl
- php5-mysql
- php5-cli
- php5-gd
- php5-mcrypt
- php5-suhosin
- php5-memcache
service:
name: php5-fpm
tasks/install.yml:
# filename: roles/php5-fpm/tasks/install.yml
#
- name: install php5-fpm and family
apt:
name: "{{ item }}"
with_items: php5.packages
notify:
- restart php5-fpm service
I want that "with_items" from install.yml look into defaults/main.yml and take that array of packages
Expand the variable
wrong
with_items: php5.packages
correct
loop: "{{ php5.packages }}"
Quoting from Loops
We added loop in Ansible 2.5. It is not yet a full replacement for with_, but we recommend it for most use cases.
We have not deprecated the use of with_ - that syntax will still be valid for the foreseeable future.
We are looking to improve loop syntax - watch this page and the changelog for updates.

Ansible - Versionfile check

I want to be able to read a versionfile if it exists, and check its contents. Then return True if the version changed or the file does not exists, False if versionfile exists and the version matches the content.
Basically this:
# setup test data
- set_fact:
version_expected: "0001"
version_path: "/path/to/version"
version_owner: "root"
version_group: "root"
# this block is used to check for version changes
- name: check version change
block:
- name: check version file
stat:
path: "{{version_path}}"
register: version_file
- set_fact:
version_remote: "{{ lookup('file', version_path) | default('') }}"
when: version_file.stat.exists
- set_fact:
version_changed: not version_file.stat.exists or version_remote != version_expected
# test writing new version
- name: write file
copy:
dest: "{{version_path}}"
content: "{{version_expected}}"
owner: "{{version_owner}}"
group: "{{version_group}}"
when: version_changed
My problem is: This is somewhat ugly and becoming quite redundant in my roles.
Is there a more elegant way to do this?
Is there maybe a module for this? (though I found none)
Or should I just write a module for this?
Best regards,
2d4r
EDIT:
im only meaning the "check version change" block, the surrounding code is for debugging only.
To be more specific, I want to download a server binary, but only if my expectet version differs from the content of the versionfile.
I want to write the new version to file, if (and only if) the download was successfull, but that is not part of my question.
EDIT2:
I got this by now:
# roles/_helper/tasks/version_check.yml
- name: check if file exists
stat:
path: "{{version_path}}"
register: version_file
- name: get remote version
slurp:
src: "{{version_path}}"
register: version_changed
when: version_file.stat.exists
# (False if versionfile exists and version is expected; True else)
- name: set return value
set_fact:
version_changed: "{{ not version_file.stat.exists or ((version_changed.content | b64decode) is version_compare(version_expected, 'ne')) }}"
used like this:
# /roles/example/tasks/main.yml
- include_role:
name: _helper
tasks_from: version_check
vars:
version_path: "{{file_version_path}}"
version_expected: "{{file_version_expected}}"
- name: doing awesome things
when: version_changed
block:
- name: download server
[...]
- name: write version
copy:
dest: "{{file_version_path}}"
content: "{{file_version_expected}}"
It kills the redundancy, but is still not what I want.
Sadly I can not register a return value from a role.
Delete everything except for write file task and remove the condition.
Ansible does this automatically for you.
- name: write file
copy:
dest: "{{version_path}}"
content: "{{version_expected}}"
owner: "{{version_owner}}"
group: "{{version_group}}"
After you changed the question, given the information provided, the only thing I can point to is to use slurp module instead of lookup, as an lookup plugins work locally in the control machine.
Compare versions using your logic or built-in version_compare filter/test.

Ansible create directories from a list

I want to create some directories from a list I have in my vars/main.yml.
- app_root:
network_prod: "/var/www/prod/network/app"
database_prod: "/var/www/prod/db/app"
My tasks/main.yml so far has this:
- name: Create application directory structure
file:
path: "{{ item }}"
state: directory
mode: 755
with_items:
- app_root
but doesn't work. I thought this could be achieved using with_dict and I also tried:
- name: Create application directory structure
file:
path: "{{ item.value }}"
state: directory
mode: 755
with_dict:
- app_root
but I got: fatal: [vagrant.dev] => with_dict expects a dict.
I've read all about looping-over-hashes, but this doesn't seem to work.
The reason I'm using this notation is because I use these variables elsewhere as well and I need to know how to call them.
I personally find it a bit easier to convert yaml to json to make sure I'm understanding it properly. Take your example:
- app_root:
network_prod: "/var/www/prod/network/app"
database_prod: "/var/www/prod/db/app"
What you have here is not a list, but a nested dictionary. If you converted this to json it would look like this:
[
{
"app_root": {
"network_prod": "/var/www/prod/network/app",
"database_prod": "/var/www/prod/db/app"
}
}
]
In order to loop through this in Ansible you would need to dereference two levels of a dictionary, the first being app_root and the second being the path elements. Unfortunately I don't think Ansible supports looping through nested dictionaries, only through nested loops.
Your best bet is probably to redo the way you're defining your paths so that you're not creating as complex a data structure. If all you're doing in this case is iterating over a list of paths in order to ensure the directories exist then I'd suggest something like this in your vars/main.yml file:
network_prod: "/var/www/prod/network/app"
database_prod: "/var/www/prod/db/app"
app_root:
- network_prod
- database_prod
Then you can have a task like this:
file: path={{ item }}
state=directory
with_items: app_root
In vars/main.yml, try removing the dash in front of app_root.
app_root:
network_prod: "/var/www/prod/network/app"
database_prod: "/var/www/prod/db/app"
I think that the approach with with_dict was correct and I believe that the only issue here is the dash - in front of app_root variable. Instead of:
- name: Create application directory structure
file:
path: "{{ item.value }}"
state: directory
mode: 755
with_dict:
- app_root
It should be:
- name: Create application directory structure
file:
path: "{{ item.value }}"
state: directory
mode: 755
with_dict: app_root
See the difference on the way the variable app_root is passed to with_dict.
A dash in YAML starts a list and the elements are not treated as variables but as literals, think of it as if you were passing an immutable string 'app_root' to with_dict (not exactly true but it helps me to think it this way) so when_dict fails to parse it because it is given a list instead of the expected dictionary. However, without a dash, with_dict is populated with the variable app_root instead and it will parse it without issues.

Resources