mirror of
https://github.com/containers/ansible-podman-collections.git
synced 2026-05-06 07:25:45 +00:00
* Fix idempotency for any podman secret driver All secret drivers are provided with the same interface in podman, so there is no need to hardcode the state as changed for all drivers other than 'file'. Signed-off-by: lersveen <7195448+lersveen@users.noreply.github.com> * ci: add tests for shell secret driver Signed-off-by: lersveen <7195448+lersveen@users.noreply.github.com> --------- Signed-off-by: lersveen <7195448+lersveen@users.noreply.github.com>
308 lines
9.6 KiB
Python
308 lines
9.6 KiB
Python
#!/usr/bin/python
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: podman_secret
|
|
author:
|
|
- "Aliaksandr Mianzhynski (@amenzhinsky)"
|
|
version_added: '1.7.0'
|
|
short_description: Manage podman secrets
|
|
notes: []
|
|
description:
|
|
- Manage podman secrets
|
|
requirements:
|
|
- podman
|
|
options:
|
|
data:
|
|
description:
|
|
- The value of the secret. Required when C(state) is C(present).
|
|
Mutually exclusive with C(env) and C(path).
|
|
type: str
|
|
driver:
|
|
description:
|
|
- Override default secrets driver, currently podman uses C(file)
|
|
which is unencrypted.
|
|
type: str
|
|
driver_opts:
|
|
description:
|
|
- Driver-specific key-value options.
|
|
type: dict
|
|
env:
|
|
description:
|
|
- The name of the environment variable that contains the secret.
|
|
Mutually exclusive with C(data) and C(path).
|
|
type: str
|
|
executable:
|
|
description:
|
|
- Path to C(podman) executable if it is not in the C($PATH) on the
|
|
machine running C(podman)
|
|
type: str
|
|
default: 'podman'
|
|
force:
|
|
description:
|
|
- Use it when C(state) is C(present) to remove and recreate an existing secret.
|
|
type: bool
|
|
default: false
|
|
skip_existing:
|
|
description:
|
|
- Use it when C(state) is C(present) and secret with the same name already exists.
|
|
If set to C(true), the secret will NOT be recreated and remains as is.
|
|
type: bool
|
|
default: false
|
|
name:
|
|
description:
|
|
- The name of the secret.
|
|
required: True
|
|
type: str
|
|
path:
|
|
description:
|
|
- Path to the file that contains the secret.
|
|
Mutually exclusive with C(data) and C(env).
|
|
type: path
|
|
state:
|
|
description:
|
|
- Whether to create or remove the named secret.
|
|
type: str
|
|
default: present
|
|
choices:
|
|
- absent
|
|
- present
|
|
labels:
|
|
description:
|
|
- Labels to set on the secret.
|
|
type: dict
|
|
debug:
|
|
description:
|
|
- Enable debug mode for module. It prints secrets diff.
|
|
type: bool
|
|
default: False
|
|
'''
|
|
|
|
EXAMPLES = r"""
|
|
- name: Create secret
|
|
containers.podman.podman_secret:
|
|
state: present
|
|
name: mysecret
|
|
data: "my super secret content"
|
|
|
|
- name: Create container that uses the secret
|
|
containers.podman.podman_container:
|
|
name: showmysecret
|
|
image: docker.io/alpine:3.14
|
|
secrets:
|
|
- mysecret
|
|
detach: false
|
|
command: cat /run/secrets/mysecret
|
|
register: container
|
|
|
|
- name: Output secret data
|
|
debug:
|
|
msg: '{{ container.stdout }}'
|
|
|
|
- name: Remove secret
|
|
containers.podman.podman_secret:
|
|
state: absent
|
|
name: mysecret
|
|
"""
|
|
|
|
import os
|
|
|
|
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_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}}"])
|
|
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, path, env, skip, 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
|
|
if skip:
|
|
return False
|
|
try:
|
|
secret = module.from_json(out)[0]
|
|
if data:
|
|
if secret['SecretData'] != data:
|
|
if debug:
|
|
diff['after'] = data
|
|
diff['before'] = secret['SecretData']
|
|
else:
|
|
diff['after'] = "<different-secret>"
|
|
diff['before'] = "<secret>"
|
|
return True
|
|
if path:
|
|
with open(path, 'rb') as f:
|
|
text = f.read().decode('utf-8')
|
|
if secret['SecretData'] != text:
|
|
if debug:
|
|
diff['after'] = text
|
|
diff['before'] = secret['SecretData']
|
|
else:
|
|
diff['after'] = "<different-secret>"
|
|
diff['before'] = "<secret>"
|
|
return True
|
|
if env:
|
|
env_data = os.environ.get(env)
|
|
if secret['SecretData'] != env_data:
|
|
if debug:
|
|
diff['after'] = env_data
|
|
diff['before'] = secret['SecretData']
|
|
else:
|
|
diff['after'] = "<different-secret>"
|
|
diff['before'] = "<secret>"
|
|
return True
|
|
if driver:
|
|
if secret['Spec']['Driver']['Name'] != driver:
|
|
diff['after'] = driver
|
|
diff['before'] = secret['Spec']['Driver']['Name']
|
|
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, path, env, 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'):
|
|
if need_update(module, executable, name, data, path, env, skip, 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:
|
|
cmd.append('--driver')
|
|
cmd.append(driver)
|
|
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)
|
|
if data:
|
|
cmd.append('-')
|
|
elif path:
|
|
cmd.append(path)
|
|
elif env:
|
|
if os.environ.get(env) is None:
|
|
module.fail_json(msg="Environment variable %s is not set" % env)
|
|
cmd.append("--env")
|
|
cmd.append(env)
|
|
|
|
if data:
|
|
rc, out, err = module.run_command(cmd, data=data, binary_data=True)
|
|
else:
|
|
rc, out, err = module.run_command(cmd)
|
|
if rc != 0:
|
|
module.fail_json(msg="Unable to create secret: %s" % err)
|
|
|
|
return {
|
|
"changed": True,
|
|
"diff": {
|
|
"before": diff['before'] + "\n",
|
|
"after": diff['after'] + "\n",
|
|
},
|
|
}
|
|
|
|
|
|
def podman_secret_remove(module, executable, name):
|
|
changed = False
|
|
rc, out, err = module.run_command([executable, 'secret', 'rm', name])
|
|
if rc == 0:
|
|
changed = True
|
|
elif 'no such secret' in err:
|
|
pass
|
|
else:
|
|
module.fail_json(msg="Unable to remove secret: %s" % err)
|
|
|
|
return {
|
|
"changed": changed,
|
|
}
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
executable=dict(type='str', default='podman'),
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
|
name=dict(type='str', required=True),
|
|
data=dict(type='str', no_log=True),
|
|
env=dict(type='str'),
|
|
path=dict(type='path'),
|
|
force=dict(type='bool', default=False),
|
|
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),
|
|
),
|
|
required_if=[('state', 'present', ['path', 'env', 'data'], True)],
|
|
mutually_exclusive=[['path', 'env', 'data']],
|
|
)
|
|
|
|
state = module.params['state']
|
|
name = module.params['name']
|
|
executable = module.get_bin_path(module.params['executable'], required=True)
|
|
|
|
if state == 'present':
|
|
data = module.params['data']
|
|
force = module.params['force']
|
|
skip = module.params['skip_existing']
|
|
driver = module.params['driver']
|
|
driver_opts = module.params['driver_opts']
|
|
debug = module.params['debug']
|
|
labels = module.params['labels']
|
|
path = module.params['path']
|
|
env = module.params['env']
|
|
results = podman_secret_create(module, executable,
|
|
name, data, path, env, force, skip,
|
|
driver, driver_opts, debug, labels)
|
|
else:
|
|
results = podman_secret_remove(module, executable, name)
|
|
|
|
module.exit_json(**results)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|