Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add idempotency for podman_secret #693

Merged
merged 2 commits into from
Dec 23, 2023
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
12 changes: 12 additions & 0 deletions plugins/module_utils/podman/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,15 @@ def normalize_signal(signal_name_or_number):
if signal_name not in _signal_map:
raise RuntimeError("Unknown signal '{0}'".format(signal_name_or_number))
return str(_signal_map[signal_name])


def get_podman_version(module, fail=True):
executable = module.params['executable'] if module.params['executable'] else 'podman'
rc, out, err = module.run_command(
[executable, b'--version'])
if rc != 0 or not out or "version" not in out:
if fail:
module.fail_json(msg="'%s --version' run failed! Error: %s" %
(executable, err))
return None
return out.split("version")[1].strip()
97 changes: 87 additions & 10 deletions plugins/modules/podman_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@
choices:
- absent
- present
labels:
description:
- Labels to set on the secret.
type: dict
debug:
description:
- Enable debug mode for module.
type: bool
default: False
'''

EXAMPLES = r"""
Expand Down Expand Up @@ -91,19 +100,75 @@
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import get_podman_version

diff = {"before": '', "after": ''}

def podman_secret_create(module, executable, name, data, force, skip,
driver, driver_opts):
if force:
module.run_command([executable, 'secret', 'rm', name])
if skip:

def podman_secret_exists(module, executable, name, version):
if version is None or LooseVersion(version) < LooseVersion('4.5.0'):
rc, out, err = module.run_command(
[executable, 'secret', 'ls', "--format", "{{.Name}}"])
if name in [i.strip() for i in out.splitlines()]:
return {
"changed": False,
}
return name in [i.strip() for i in out.splitlines()]
rc, out, err = module.run_command(
[executable, 'secret', 'exists', name])
return rc == 0


def need_update(module, executable, name, data, driver, driver_opts, debug, labels):

cmd = [executable, 'secret', 'inspect', '--showsecret', name]
rc, out, err = module.run_command(cmd)
if rc != 0:
if debug:
module.log("PODMAN-SECRET-DEBUG: Unable to get secret info: %s" % err)
return True
try:
secret = module.from_json(out)[0]
# We support only file driver for now
if (driver and driver != 'file') or secret['Spec']['Driver']['Name'] != 'file':
if debug:
module.log("PODMAN-SECRET-DEBUG: Idempotency of driver %s is not supported" % driver)
return True
if secret['SecretData'] != data:
diff['after'] = "<different-secret>"
diff['before'] = "<secret>"
return True
if driver_opts:
for k, v in driver_opts.items():
if secret['Spec']['Driver']['Options'].get(k) != v:
diff['after'] = "=".join([k, v])
diff['before'] = "=".join(
[k, secret['Spec']['Driver']['Options'].get(k)])
return True
if labels:
for k, v in labels.items():
if secret['Spec']['Labels'].get(k) != v:
diff['after'] = "=".join([k, v])
diff['before'] = "=".join(
[k, secret['Spec']['Labels'].get(k)])
return True
except Exception:
return True
return False


def podman_secret_create(module, executable, name, data, force, skip,
driver, driver_opts, debug, labels):
podman_version = get_podman_version(module, fail=False)
if (podman_version is not None and
LooseVersion(podman_version) >= LooseVersion('4.7.0')
and (driver is None or driver == 'file')):
if not skip and need_update(module, executable, name, data, driver, driver_opts, debug, labels):
podman_secret_remove(module, executable, name)
else:
return {"changed": False}
else:
if force:
podman_secret_remove(module, executable, name)
if skip and podman_secret_exists(module, executable, name, podman_version):
return {"changed": False}

cmd = [executable, 'secret', 'create']
if driver:
Expand All @@ -112,6 +177,10 @@ def podman_secret_create(module, executable, name, data, force, skip,
if driver_opts:
cmd.append('--driver-opts')
cmd.append(",".join("=".join(i) for i in driver_opts.items()))
if labels:
for k, v in labels.items():
cmd.append('--label')
cmd.append("=".join([k, v]))
cmd.append(name)
cmd.append('-')

Expand All @@ -121,6 +190,10 @@ def podman_secret_create(module, executable, name, data, force, skip,

return {
"changed": True,
"diff": {
"before": diff['before'] + "\n",
"after": diff['after'] + "\n",
},
}


Expand Down Expand Up @@ -150,6 +223,8 @@ def main():
skip_existing=dict(type='bool', default=False),
driver=dict(type='str'),
driver_opts=dict(type='dict'),
labels=dict(type='dict'),
debug=dict(type='bool', default=False),
),
)

Expand All @@ -165,9 +240,11 @@ def main():
skip = module.params['skip_existing']
driver = module.params['driver']
driver_opts = module.params['driver_opts']
debug = module.params['debug']
labels = module.params['labels']
results = podman_secret_create(module, executable,
name, data, force, skip,
driver, driver_opts)
driver, driver_opts, debug, labels)
else:
results = podman_secret_remove(module, executable, name)

Expand Down
176 changes: 176 additions & 0 deletions tests/integration/targets/podman_secret/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
- name: Test podman_secret
block:

- name: Discover podman version
shell: podman version | grep "^Version:" | awk {'print $2'}
register: podman_v

- name: Set podman version fact
set_fact:
podman_version: "{{ podman_v.stdout | string }}"

- name: Set podman version fact to gt than 4.7.0 if so
set_fact:
podman_version_gt470: "{{ podman_version is version('4.7.0', '>=') }}"

- name: Make sure secret doesn't exist
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
Expand All @@ -11,6 +23,7 @@
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
debug: true
data: secret content

- name: Recreate secret
Expand All @@ -27,13 +40,33 @@
name: mysecret
data: super secret content
skip_existing: true
debug: true
register: skipped

- name: Force secret to same
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: super secret content
force: true
debug: true
register: forced2

- name: Check assertions
assert:
that:
- forced is changed
- skipped is not changed
- forced2 is changed
when: not podman_version_gt470

- name: Check assertions for podman >= 4.7.0
assert:
that:
- forced is changed
- skipped is not changed
- forced2 is not changed
when: podman_version_gt470

- name: Create container that uses secret
containers.podman.podman_container:
Expand Down Expand Up @@ -93,6 +126,24 @@
that:
- removed is not changed

- name: Create secret with file driver labels
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
labels:
lab1: somestringhere
label2: "some value is there"
"long label": onestring
"boring label": "multi string value"

- name: Remove secret
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
state: absent
name: mysecret

- name: Create secret with file driver and custom options
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
Expand All @@ -103,6 +154,131 @@
a: b
c: d

- when: podman_version_gt470
block:

- name: Create secret with file driver and different options
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
driver_opts:
a: b
c: e
register: opts_changed

- name: Create secret with file driver and different options again
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
driver_opts:
a: b
c: e
register: opts_changed2

- name: Create secret with different content
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: other secret content
driver_opts:
a: b
c: e
register: secret_changed

- name: Create secret with different content again
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: other secret content
driver_opts:
a: b
c: e
register: secret_changed2

- name: Create secret with different content but skipped
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: other secret content and skipped
skip_existing: true
debug: true
driver_opts:
a: b
c: e
register: skip_secret_changed

- name: Create secret with different content but forced
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: other secret content and skipped
force: true
debug: true
driver_opts:
a: b
c: e
register: force_secret_changed

- name: Check opts changes
assert:
that:
- opts_changed is changed
- opts_changed2 is not changed
- secret_changed is changed
- secret_changed2 is not changed
- skip_secret_changed is not changed
- force_secret_changed is changed

- name: Create secret with file driver and labels
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
labels:
lab1: somestringhere
label2: "some value is there"
"long label": onestring
"boring label": "multi string value"
register: secretlabels

- name: Create secret with file driver and labels again
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
labels:
lab1: somestringhere
label2: "some value is there"
"long label": onestring
"boring label": "multi string value"
register: secretlabels2

- name: Create secret with file driver and different labels
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
name: mysecret
data: secret content
driver: file
labels:
lab1: somestringhere
label2: "some value is not there"
"long label": onestring
"boring label": "multi string value"
register: secretlabels3

- name: Check labels changes
assert:
that:
- secretlabels is changed
- secretlabels2 is not changed
- secretlabels3 is changed

- name: Remove secret
containers.podman.podman_secret:
executable: "{{ test_executable | default('podman') }}"
Expand Down