mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-06 21:12:37 +00:00
Enable black formatting test (#259)
Enable black formatting test SUMMARY Signed-off-by: Abhijeet Kasurde akasurde@redhat.com ISSUE TYPE Bugfix Pull Request COMPONENT NAME plugins/action/k8s_info.py plugins/connection/kubectl.py plugins/doc_fragments/helm_common_options.py plugins/doc_fragments/k8s_auth_options.py plugins/doc_fragments/k8s_delete_options.py plugins/doc_fragments/k8s_name_options.py plugins/doc_fragments/k8s_resource_options.py plugins/doc_fragments/k8s_scale_options.py plugins/doc_fragments/k8s_state_options.py plugins/doc_fragments/k8s_wait_options.py plugins/filter/k8s.py plugins/inventory/k8s.py plugins/lookup/k8s.py plugins/lookup/kustomize.py plugins/module_utils/ansiblemodule.py plugins/module_utils/apply.py plugins/module_utils/args_common.py plugins/module_utils/client/discovery.py plugins/module_utils/client/resource.py plugins/module_utils/common.py plugins/module_utils/exceptions.py plugins/module_utils/hashes.py plugins/module_utils/helm.py plugins/module_utils/k8sdynamicclient.py plugins/module_utils/selector.py plugins/modules/helm.py plugins/modules/helm_info.py plugins/modules/helm_plugin.py plugins/modules/helm_plugin_info.py plugins/modules/helm_repository.py plugins/modules/helm_template.py plugins/modules/k8s.py plugins/modules/k8s_cluster_info.py plugins/modules/k8s_cp.py plugins/modules/k8s_drain.py plugins/modules/k8s_exec.py plugins/modules/k8s_info.py plugins/modules/k8s_json_patch.py plugins/modules/k8s_log.py plugins/modules/k8s_rollback.py plugins/modules/k8s_scale.py plugins/modules/k8s_service.py tests/integration/targets/kubernetes/library/test_tempfile.py tests/unit/module_utils/test_apply.py tests/unit/module_utils/test_common.py tests/unit/module_utils/test_discoverer.py tests/unit/module_utils/test_hashes.py tests/unit/module_utils/test_marshal.py tests/unit/module_utils/test_selector.py tox.ini Reviewed-by: None <None> Reviewed-by: Mike Graves <mgraves@redhat.com> Reviewed-by: None <None>
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
# Copyright: Ansible Project
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm
|
||||
|
||||
@@ -159,9 +160,9 @@ options:
|
||||
version_added: "2.2.0"
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.helm_common_options
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
|
||||
kubernetes.core.helm:
|
||||
name: test
|
||||
@@ -248,7 +249,7 @@ EXAMPLES = r'''
|
||||
enabled: True
|
||||
logging:
|
||||
enabled: True
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
status:
|
||||
@@ -311,6 +312,7 @@ from distutils.version import LooseVersion
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
IMP_YAML = True
|
||||
except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
@@ -333,7 +335,7 @@ def get_release(state, release_name):
|
||||
|
||||
if state is not None:
|
||||
for release in state:
|
||||
if release['name'] == release_name:
|
||||
if release["name"] == release_name:
|
||||
return release
|
||||
return None
|
||||
|
||||
@@ -352,7 +354,7 @@ def get_release_status(module, command, release_name):
|
||||
if release is None: # not install
|
||||
return None
|
||||
|
||||
release['values'] = get_values(module, command, release_name)
|
||||
release["values"] = get_values(module, command, release_name)
|
||||
|
||||
return release
|
||||
|
||||
@@ -376,9 +378,23 @@ def fetch_chart_info(module, command, chart_ref):
|
||||
return yaml.safe_load(out)
|
||||
|
||||
|
||||
def deploy(command, release_name, release_values, chart_name, wait,
|
||||
wait_timeout, disable_hook, force, values_files, history_max, atomic=False,
|
||||
create_namespace=False, replace=False, skip_crds=False, timeout=None):
|
||||
def deploy(
|
||||
command,
|
||||
release_name,
|
||||
release_values,
|
||||
chart_name,
|
||||
wait,
|
||||
wait_timeout,
|
||||
disable_hook,
|
||||
force,
|
||||
values_files,
|
||||
history_max,
|
||||
atomic=False,
|
||||
create_namespace=False,
|
||||
replace=False,
|
||||
skip_crds=False,
|
||||
timeout=None,
|
||||
):
|
||||
"""
|
||||
Install/upgrade/rollback release chart
|
||||
"""
|
||||
@@ -419,8 +435,8 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
||||
deploy_command += " --values=" + value_file
|
||||
|
||||
if release_values != {}:
|
||||
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||
with open(path, 'w') as yaml_file:
|
||||
fd, path = tempfile.mkstemp(suffix=".yml")
|
||||
with open(path, "w") as yaml_file:
|
||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||
deploy_command += " -f=" + path
|
||||
|
||||
@@ -434,8 +450,7 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
||||
return deploy_command
|
||||
|
||||
|
||||
def delete(command, release_name, purge, disable_hook,
|
||||
wait, wait_timeout):
|
||||
def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
|
||||
"""
|
||||
Delete release chart
|
||||
"""
|
||||
@@ -462,7 +477,7 @@ def delete(command, release_name, purge, disable_hook,
|
||||
def load_values_files(values_files):
|
||||
values = {}
|
||||
for values_file in values_files or []:
|
||||
with open(values_file, 'r') as fd:
|
||||
with open(values_file, "r") as fd:
|
||||
content = yaml.safe_load(fd)
|
||||
if not isinstance(content, dict):
|
||||
continue
|
||||
@@ -489,8 +504,16 @@ def has_plugin(command, plugin):
|
||||
return False
|
||||
|
||||
|
||||
def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
||||
values_files=None, chart_version=None, replace=False):
|
||||
def helmdiff_check(
|
||||
module,
|
||||
helm_cmd,
|
||||
release_name,
|
||||
chart_ref,
|
||||
release_values,
|
||||
values_files=None,
|
||||
chart_version=None,
|
||||
replace=False,
|
||||
):
|
||||
"""
|
||||
Use helm diff to determine if a release would change by upgrading a chart.
|
||||
"""
|
||||
@@ -504,8 +527,8 @@ def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
||||
cmd += " " + "--reset-values"
|
||||
|
||||
if release_values != {}:
|
||||
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||
with open(path, 'w') as yaml_file:
|
||||
fd, path = tempfile.mkstemp(suffix=".yml")
|
||||
with open(path, "w") as yaml_file:
|
||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||
cmd += " -f=" + path
|
||||
|
||||
@@ -522,60 +545,83 @@ def default_check(release_status, chart_info, values=None, values_files=None):
|
||||
Use default check to determine if release would change by upgrading a chart.
|
||||
"""
|
||||
# the 'appVersion' specification is optional in a chart
|
||||
chart_app_version = chart_info.get('appVersion', None)
|
||||
released_app_version = release_status.get('app_version', None)
|
||||
chart_app_version = chart_info.get("appVersion", None)
|
||||
released_app_version = release_status.get("app_version", None)
|
||||
|
||||
# when deployed without an 'appVersion' chart value the 'helm list' command will return the entry `app_version: ""`
|
||||
appversion_is_same = (chart_app_version == released_app_version) or (chart_app_version is None and released_app_version == "")
|
||||
appversion_is_same = (chart_app_version == released_app_version) or (
|
||||
chart_app_version is None and released_app_version == ""
|
||||
)
|
||||
|
||||
if values_files:
|
||||
values_match = release_status['values'] == load_values_files(values_files)
|
||||
values_match = release_status["values"] == load_values_files(values_files)
|
||||
else:
|
||||
values_match = release_status['values'] == values
|
||||
return not values_match \
|
||||
or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"] \
|
||||
values_match = release_status["values"] == values
|
||||
return (
|
||||
not values_match
|
||||
or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
|
||||
or not appversion_is_same
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
global module
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
chart_ref=dict(type='path'),
|
||||
chart_repo_url=dict(type='str'),
|
||||
chart_version=dict(type='str'),
|
||||
release_name=dict(type='str', required=True, aliases=['name']),
|
||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
||||
release_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
|
||||
release_values=dict(type='dict', default={}, aliases=['values']),
|
||||
values_files=dict(type='list', default=[], elements='str'),
|
||||
update_repo_cache=dict(type='bool', default=False),
|
||||
|
||||
binary_path=dict(type="path"),
|
||||
chart_ref=dict(type="path"),
|
||||
chart_repo_url=dict(type="str"),
|
||||
chart_version=dict(type="str"),
|
||||
release_name=dict(type="str", required=True, aliases=["name"]),
|
||||
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
|
||||
release_state=dict(
|
||||
default="present", choices=["present", "absent"], aliases=["state"]
|
||||
),
|
||||
release_values=dict(type="dict", default={}, aliases=["values"]),
|
||||
values_files=dict(type="list", default=[], elements="str"),
|
||||
update_repo_cache=dict(type="bool", default=False),
|
||||
# Helm options
|
||||
disable_hook=dict(type='bool', default=False),
|
||||
force=dict(type='bool', default=False),
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
purge=dict(type='bool', default=True),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='str'),
|
||||
timeout=dict(type='str'),
|
||||
atomic=dict(type='bool', default=False),
|
||||
create_namespace=dict(type='bool', default=False),
|
||||
replace=dict(type='bool', default=False),
|
||||
skip_crds=dict(type='bool', default=False),
|
||||
history_max=dict(type='int'),
|
||||
|
||||
disable_hook=dict(type="bool", default=False),
|
||||
force=dict(type="bool", default=False),
|
||||
context=dict(
|
||||
type="str",
|
||||
aliases=["kube_context"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
||||
),
|
||||
kubeconfig=dict(
|
||||
type="path",
|
||||
aliases=["kubeconfig_path"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
||||
),
|
||||
purge=dict(type="bool", default=True),
|
||||
wait=dict(type="bool", default=False),
|
||||
wait_timeout=dict(type="str"),
|
||||
timeout=dict(type="str"),
|
||||
atomic=dict(type="bool", default=False),
|
||||
create_namespace=dict(type="bool", default=False),
|
||||
replace=dict(type="bool", default=False),
|
||||
skip_crds=dict(type="bool", default=False),
|
||||
history_max=dict(type="int"),
|
||||
# Generic auth key
|
||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
|
||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
||||
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
|
||||
ca_cert=dict(
|
||||
type="path",
|
||||
aliases=["ssl_ca_cert"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
|
||||
),
|
||||
validate_certs=dict(
|
||||
type="bool",
|
||||
default=True,
|
||||
aliases=["verify_ssl"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
|
||||
),
|
||||
api_key=dict(
|
||||
type="str", no_log=True, fallback=(env_fallback, ["K8S_AUTH_API_KEY"])
|
||||
),
|
||||
),
|
||||
required_if=[
|
||||
('release_state', 'present', ['release_name', 'chart_ref']),
|
||||
('release_state', 'absent', ['release_name'])
|
||||
("release_state", "present", ["release_name", "chart_ref"]),
|
||||
("release_state", "absent", ["release_name"]),
|
||||
],
|
||||
mutually_exclusive=[
|
||||
("context", "ca_cert"),
|
||||
@@ -591,33 +637,33 @@ def main():
|
||||
|
||||
changed = False
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
chart_ref = module.params.get('chart_ref')
|
||||
chart_repo_url = module.params.get('chart_repo_url')
|
||||
chart_version = module.params.get('chart_version')
|
||||
release_name = module.params.get('release_name')
|
||||
release_state = module.params.get('release_state')
|
||||
release_values = module.params.get('release_values')
|
||||
values_files = module.params.get('values_files')
|
||||
update_repo_cache = module.params.get('update_repo_cache')
|
||||
bin_path = module.params.get("binary_path")
|
||||
chart_ref = module.params.get("chart_ref")
|
||||
chart_repo_url = module.params.get("chart_repo_url")
|
||||
chart_version = module.params.get("chart_version")
|
||||
release_name = module.params.get("release_name")
|
||||
release_state = module.params.get("release_state")
|
||||
release_values = module.params.get("release_values")
|
||||
values_files = module.params.get("values_files")
|
||||
update_repo_cache = module.params.get("update_repo_cache")
|
||||
|
||||
# Helm options
|
||||
disable_hook = module.params.get('disable_hook')
|
||||
force = module.params.get('force')
|
||||
purge = module.params.get('purge')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
atomic = module.params.get('atomic')
|
||||
create_namespace = module.params.get('create_namespace')
|
||||
replace = module.params.get('replace')
|
||||
skip_crds = module.params.get('skip_crds')
|
||||
history_max = module.params.get('history_max')
|
||||
timeout = module.params.get('timeout')
|
||||
disable_hook = module.params.get("disable_hook")
|
||||
force = module.params.get("force")
|
||||
purge = module.params.get("purge")
|
||||
wait = module.params.get("wait")
|
||||
wait_timeout = module.params.get("wait_timeout")
|
||||
atomic = module.params.get("atomic")
|
||||
create_namespace = module.params.get("create_namespace")
|
||||
replace = module.params.get("replace")
|
||||
skip_crds = module.params.get("skip_crds")
|
||||
history_max = module.params.get("history_max")
|
||||
timeout = module.params.get("timeout")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = module.get_bin_path('helm', required=True)
|
||||
helm_cmd_common = module.get_bin_path("helm", required=True)
|
||||
|
||||
if update_repo_cache:
|
||||
run_repo_update(module, helm_cmd_common)
|
||||
@@ -635,11 +681,15 @@ def main():
|
||||
if wait:
|
||||
helm_version = get_helm_version(module, helm_cmd_common)
|
||||
if LooseVersion(helm_version) < LooseVersion("3.7.0"):
|
||||
opt_result['warnings'] = []
|
||||
opt_result['warnings'].append("helm uninstall support option --wait for helm release >= 3.7.0")
|
||||
opt_result["warnings"] = []
|
||||
opt_result["warnings"].append(
|
||||
"helm uninstall support option --wait for helm release >= 3.7.0"
|
||||
)
|
||||
wait = False
|
||||
|
||||
helm_cmd = delete(helm_cmd, release_name, purge, disable_hook, wait, wait_timeout)
|
||||
helm_cmd = delete(
|
||||
helm_cmd, release_name, purge, disable_hook, wait, wait_timeout
|
||||
)
|
||||
changed = True
|
||||
elif release_state == "present":
|
||||
|
||||
@@ -653,54 +703,87 @@ def main():
|
||||
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
|
||||
|
||||
if release_status is None: # Not installed
|
||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||
disable_hook, False, values_files=values_files, atomic=atomic,
|
||||
create_namespace=create_namespace, replace=replace,
|
||||
skip_crds=skip_crds, history_max=history_max, timeout=timeout)
|
||||
helm_cmd = deploy(
|
||||
helm_cmd,
|
||||
release_name,
|
||||
release_values,
|
||||
chart_ref,
|
||||
wait,
|
||||
wait_timeout,
|
||||
disable_hook,
|
||||
False,
|
||||
values_files=values_files,
|
||||
atomic=atomic,
|
||||
create_namespace=create_namespace,
|
||||
replace=replace,
|
||||
skip_crds=skip_crds,
|
||||
history_max=history_max,
|
||||
timeout=timeout,
|
||||
)
|
||||
changed = True
|
||||
|
||||
else:
|
||||
|
||||
if has_plugin(helm_cmd_common, "diff") and not chart_repo_url:
|
||||
would_change = helmdiff_check(module, helm_cmd_common, release_name, chart_ref,
|
||||
release_values, values_files, chart_version, replace)
|
||||
would_change = helmdiff_check(
|
||||
module,
|
||||
helm_cmd_common,
|
||||
release_name,
|
||||
chart_ref,
|
||||
release_values,
|
||||
values_files,
|
||||
chart_version,
|
||||
replace,
|
||||
)
|
||||
else:
|
||||
module.warn("The default idempotency check can fail to report changes in certain cases. "
|
||||
"Install helm diff for better results.")
|
||||
would_change = default_check(release_status, chart_info, release_values, values_files)
|
||||
module.warn(
|
||||
"The default idempotency check can fail to report changes in certain cases. "
|
||||
"Install helm diff for better results."
|
||||
)
|
||||
would_change = default_check(
|
||||
release_status, chart_info, release_values, values_files
|
||||
)
|
||||
|
||||
if force or would_change:
|
||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||
disable_hook, force, values_files=values_files, atomic=atomic,
|
||||
create_namespace=create_namespace, replace=replace,
|
||||
skip_crds=skip_crds, history_max=history_max, timeout=timeout)
|
||||
helm_cmd = deploy(
|
||||
helm_cmd,
|
||||
release_name,
|
||||
release_values,
|
||||
chart_ref,
|
||||
wait,
|
||||
wait_timeout,
|
||||
disable_hook,
|
||||
force,
|
||||
values_files=values_files,
|
||||
atomic=atomic,
|
||||
create_namespace=create_namespace,
|
||||
replace=replace,
|
||||
skip_crds=skip_crds,
|
||||
history_max=history_max,
|
||||
timeout=timeout,
|
||||
)
|
||||
changed = True
|
||||
|
||||
if module.check_mode:
|
||||
check_status = {
|
||||
'values': {
|
||||
"current": {},
|
||||
"declared": {},
|
||||
}
|
||||
}
|
||||
check_status = {"values": {"current": {}, "declared": {}}}
|
||||
if release_status:
|
||||
check_status['values']['current'] = release_status['values']
|
||||
check_status['values']['declared'] = release_status
|
||||
check_status["values"]["current"] = release_status["values"]
|
||||
check_status["values"]["declared"] = release_status
|
||||
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
command=helm_cmd,
|
||||
status=check_status,
|
||||
stdout='',
|
||||
stderr='',
|
||||
stdout="",
|
||||
stderr="",
|
||||
**opt_result,
|
||||
)
|
||||
elif not changed:
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
status=release_status,
|
||||
stdout='',
|
||||
stderr='',
|
||||
stdout="",
|
||||
stderr="",
|
||||
command=helm_cmd,
|
||||
**opt_result,
|
||||
)
|
||||
@@ -717,5 +800,5 @@ def main():
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm_info
|
||||
|
||||
@@ -40,16 +41,16 @@ options:
|
||||
aliases: [ namespace ]
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.helm_common_options
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Deploy latest version of Grafana chart inside monitoring namespace
|
||||
kubernetes.core.helm_info:
|
||||
name: test
|
||||
release_namespace: monitoring
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
status:
|
||||
type: complex
|
||||
description: A dictionary of status output
|
||||
@@ -87,26 +88,30 @@ status:
|
||||
type: str
|
||||
returned: always
|
||||
description: Dict of Values used to deploy
|
||||
'''
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
IMP_YAML = True
|
||||
except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
IMP_YAML = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm, get_values
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||
run_helm,
|
||||
get_values,
|
||||
)
|
||||
|
||||
|
||||
# Get Release from all deployed releases
|
||||
def get_release(state, release_name):
|
||||
if state is not None:
|
||||
for release in state:
|
||||
if release['name'] == release_name:
|
||||
if release["name"] == release_name:
|
||||
return release
|
||||
return None
|
||||
|
||||
@@ -119,8 +124,10 @@ def get_release_status(module, command, release_name):
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=list_command
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||
rc, out, err
|
||||
),
|
||||
command=list_command,
|
||||
)
|
||||
|
||||
release = get_release(yaml.safe_load(out), release_name)
|
||||
@@ -128,7 +135,7 @@ def get_release_status(module, command, release_name):
|
||||
if release is None: # not install
|
||||
return None
|
||||
|
||||
release['values'] = get_values(module, command, release_name)
|
||||
release["values"] = get_values(module, command, release_name)
|
||||
|
||||
return release
|
||||
|
||||
@@ -138,25 +145,42 @@ def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
release_name=dict(type='str', required=True, aliases=['name']),
|
||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
||||
|
||||
binary_path=dict(type="path"),
|
||||
release_name=dict(type="str", required=True, aliases=["name"]),
|
||||
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
|
||||
# Helm options
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
|
||||
context=dict(
|
||||
type="str",
|
||||
aliases=["kube_context"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
||||
),
|
||||
kubeconfig=dict(
|
||||
type="path",
|
||||
aliases=["kubeconfig_path"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
||||
),
|
||||
# Generic auth key
|
||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
|
||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
||||
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
|
||||
ca_cert=dict(
|
||||
type="path",
|
||||
aliases=["ssl_ca_cert"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
|
||||
),
|
||||
validate_certs=dict(
|
||||
type="bool",
|
||||
default=True,
|
||||
aliases=["verify_ssl"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
|
||||
),
|
||||
api_key=dict(
|
||||
type="str", no_log=True, fallback=(env_fallback, ["K8S_AUTH_API_KEY"])
|
||||
),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
("context", "ca_cert"),
|
||||
("context", "validate_certs"),
|
||||
("kubeconfig", "ca_cert"),
|
||||
("kubeconfig", "validate_certs")
|
||||
("kubeconfig", "validate_certs"),
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -164,13 +188,13 @@ def main():
|
||||
if not IMP_YAML:
|
||||
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
release_name = module.params.get('release_name')
|
||||
bin_path = module.params.get("binary_path")
|
||||
release_name = module.params.get("release_name")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = module.get_bin_path('helm', required=True)
|
||||
helm_cmd_common = module.get_bin_path("helm", required=True)
|
||||
|
||||
release_status = get_release_status(module, helm_cmd_common, release_name)
|
||||
|
||||
@@ -180,5 +204,5 @@ def main():
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm_plugin
|
||||
short_description: Manage Helm plugins
|
||||
@@ -50,9 +50,9 @@ options:
|
||||
version_added: "2.3.0"
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.helm_common_options
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Install Helm env plugin
|
||||
kubernetes.core.helm_plugin:
|
||||
plugin_path: https://github.com/adamreese/helm-env
|
||||
@@ -78,9 +78,9 @@ EXAMPLES = r'''
|
||||
kubernetes.core.helm_plugin:
|
||||
plugin_name: secrets
|
||||
state: latest
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
stdout:
|
||||
type: str
|
||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||
@@ -106,33 +106,53 @@ rc:
|
||||
description: Helm plugin command return code
|
||||
returned: always
|
||||
sample: 1
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||
run_helm,
|
||||
get_helm_plugin_list,
|
||||
parse_helm_plugin_list
|
||||
parse_helm_plugin_list,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent', 'latest']),
|
||||
plugin_path=dict(type='str',),
|
||||
plugin_name=dict(type='str',),
|
||||
plugin_version=dict(type='str',),
|
||||
binary_path=dict(type="path"),
|
||||
state=dict(
|
||||
type="str", default="present", choices=["present", "absent", "latest"]
|
||||
),
|
||||
plugin_path=dict(type="str",),
|
||||
plugin_name=dict(type="str",),
|
||||
plugin_version=dict(type="str",),
|
||||
# Helm options
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
|
||||
context=dict(
|
||||
type="str",
|
||||
aliases=["kube_context"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
||||
),
|
||||
kubeconfig=dict(
|
||||
type="path",
|
||||
aliases=["kubeconfig_path"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
||||
),
|
||||
# Generic auth key
|
||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
|
||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
||||
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
|
||||
ca_cert=dict(
|
||||
type="path",
|
||||
aliases=["ssl_ca_cert"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
|
||||
),
|
||||
validate_certs=dict(
|
||||
type="bool",
|
||||
default=True,
|
||||
aliases=["verify_ssl"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
|
||||
),
|
||||
api_key=dict(
|
||||
type="str", no_log=True, fallback=(env_fallback, ["K8S_AUTH_API_KEY"])
|
||||
),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
@@ -141,37 +161,37 @@ def main():
|
||||
("state", "latest", ("plugin_name",)),
|
||||
],
|
||||
mutually_exclusive=[
|
||||
('plugin_name', 'plugin_path'),
|
||||
("plugin_name", "plugin_path"),
|
||||
("context", "ca_cert"),
|
||||
("context", "validate_certs"),
|
||||
("kubeconfig", "ca_cert"),
|
||||
("kubeconfig", "validate_certs")
|
||||
("kubeconfig", "validate_certs"),
|
||||
],
|
||||
)
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
state = module.params.get('state')
|
||||
bin_path = module.params.get("binary_path")
|
||||
state = module.params.get("state")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = 'helm'
|
||||
helm_cmd_common = "helm"
|
||||
|
||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||
|
||||
helm_cmd_common += " plugin"
|
||||
|
||||
if state == 'present':
|
||||
helm_cmd_common += " install %s" % module.params.get('plugin_path')
|
||||
plugin_version = module.params.get('plugin_version')
|
||||
if state == "present":
|
||||
helm_cmd_common += " install %s" % module.params.get("plugin_path")
|
||||
plugin_version = module.params.get("plugin_version")
|
||||
if plugin_version is not None:
|
||||
helm_cmd_common += " --version=%s" % plugin_version
|
||||
if not module.check_mode:
|
||||
rc, out, err = run_helm(module, helm_cmd_common, fails_on_error=False)
|
||||
else:
|
||||
rc, out, err = (0, '', '')
|
||||
rc, out, err = (0, "", "")
|
||||
|
||||
if rc == 1 and 'plugin already exists' in err:
|
||||
if rc == 1 and "plugin already exists" in err:
|
||||
module.exit_json(
|
||||
failed=False,
|
||||
changed=False,
|
||||
@@ -179,7 +199,7 @@ def main():
|
||||
command=helm_cmd_common,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
elif rc == 0:
|
||||
module.exit_json(
|
||||
@@ -199,8 +219,8 @@ def main():
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
elif state == 'absent':
|
||||
plugin_name = module.params.get('plugin_name')
|
||||
elif state == "absent":
|
||||
plugin_name = module.params.get("plugin_name")
|
||||
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||
|
||||
@@ -212,7 +232,7 @@ def main():
|
||||
command=helm_cmd_common + " list",
|
||||
stdout=output,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
found = False
|
||||
@@ -228,14 +248,14 @@ def main():
|
||||
command=helm_cmd_common + " list",
|
||||
stdout=output,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
|
||||
if not module.check_mode:
|
||||
rc, out, err = run_helm(module, helm_uninstall_cmd, fails_on_error=False)
|
||||
else:
|
||||
rc, out, err = (0, '', '')
|
||||
rc, out, err = (0, "", "")
|
||||
|
||||
if rc == 0:
|
||||
module.exit_json(
|
||||
@@ -244,7 +264,7 @@ def main():
|
||||
command=helm_uninstall_cmd,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin uninstall",
|
||||
@@ -253,8 +273,8 @@ def main():
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
elif state == 'latest':
|
||||
plugin_name = module.params.get('plugin_name')
|
||||
elif state == "latest":
|
||||
plugin_name = module.params.get("plugin_name")
|
||||
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||
|
||||
@@ -266,7 +286,7 @@ def main():
|
||||
command=helm_cmd_common + " list",
|
||||
stdout=output,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
found = False
|
||||
@@ -282,14 +302,14 @@ def main():
|
||||
command=helm_cmd_common + " list",
|
||||
stdout=output,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
helm_update_cmd = "%s update %s" % (helm_cmd_common, plugin_name)
|
||||
if not module.check_mode:
|
||||
rc, out, err = run_helm(module, helm_update_cmd, fails_on_error=False)
|
||||
else:
|
||||
rc, out, err = (0, '', '')
|
||||
rc, out, err = (0, "", "")
|
||||
|
||||
if rc == 0:
|
||||
module.exit_json(
|
||||
@@ -298,7 +318,7 @@ def main():
|
||||
command=helm_update_cmd,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
rc=rc,
|
||||
)
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin update",
|
||||
@@ -309,5 +329,5 @@ def main():
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm_plugin_info
|
||||
short_description: Gather information about Helm plugins
|
||||
@@ -27,18 +27,18 @@ options:
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.helm_common_options
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Gather Helm plugin info
|
||||
kubernetes.core.helm_plugin_info:
|
||||
|
||||
- name: Gather Helm env plugin info
|
||||
kubernetes.core.helm_plugin_info:
|
||||
plugin_name: env
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
stdout:
|
||||
type: str
|
||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||
@@ -68,7 +68,7 @@ rc:
|
||||
description: Helm plugin command return code
|
||||
returned: always
|
||||
sample: 1
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||
@@ -80,39 +80,57 @@ from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
plugin_name=dict(type='str',),
|
||||
binary_path=dict(type="path"),
|
||||
plugin_name=dict(type="str",),
|
||||
# Helm options
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
|
||||
context=dict(
|
||||
type="str",
|
||||
aliases=["kube_context"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
||||
),
|
||||
kubeconfig=dict(
|
||||
type="path",
|
||||
aliases=["kubeconfig_path"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
||||
),
|
||||
# Generic auth key
|
||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
|
||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
||||
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
|
||||
ca_cert=dict(
|
||||
type="path",
|
||||
aliases=["ssl_ca_cert"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
|
||||
),
|
||||
validate_certs=dict(
|
||||
type="bool",
|
||||
default=True,
|
||||
aliases=["verify_ssl"],
|
||||
fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
|
||||
),
|
||||
api_key=dict(
|
||||
type="str", no_log=True, fallback=(env_fallback, ["K8S_AUTH_API_KEY"])
|
||||
),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
("context", "ca_cert"),
|
||||
("context", "validate_certs"),
|
||||
("kubeconfig", "ca_cert"),
|
||||
("kubeconfig", "validate_certs")
|
||||
("kubeconfig", "validate_certs"),
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
bin_path = module.params.get("binary_path")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = 'helm'
|
||||
helm_cmd_common = "helm"
|
||||
|
||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||
|
||||
helm_cmd_common += " plugin"
|
||||
|
||||
plugin_name = module.params.get('plugin_name')
|
||||
plugin_name = module.params.get("plugin_name")
|
||||
|
||||
plugin_list = []
|
||||
|
||||
@@ -123,21 +141,13 @@ def main():
|
||||
for line in out:
|
||||
if plugin_name is None:
|
||||
plugin_list.append(
|
||||
{
|
||||
"name": line[0],
|
||||
"version": line[1],
|
||||
"description": line[2],
|
||||
}
|
||||
{"name": line[0], "version": line[1], "description": line[2]}
|
||||
)
|
||||
continue
|
||||
|
||||
if plugin_name == line[0]:
|
||||
plugin_list.append(
|
||||
{
|
||||
"name": line[0],
|
||||
"version": line[1],
|
||||
"description": line[2],
|
||||
}
|
||||
{"name": line[0], "version": line[1], "description": line[2]}
|
||||
)
|
||||
break
|
||||
|
||||
@@ -151,5 +161,5 @@ def main():
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm_repository
|
||||
|
||||
@@ -64,9 +65,9 @@ options:
|
||||
default: present
|
||||
aliases: [ state ]
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Add a repository
|
||||
kubernetes.core.helm_repository:
|
||||
name: stable
|
||||
@@ -76,9 +77,9 @@ EXAMPLES = r'''
|
||||
kubernetes.core.helm_repository:
|
||||
name: redhat-charts
|
||||
repo_url: https://redhat-developer.github.com/redhat-helm-charts
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
stdout:
|
||||
type: str
|
||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||
@@ -109,12 +110,13 @@ msg:
|
||||
description: Error message returned by `helm` command
|
||||
returned: on failure
|
||||
sample: 'Repository already have a repository named bitnami'
|
||||
'''
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
IMP_YAML = True
|
||||
except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
@@ -128,7 +130,7 @@ from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_he
|
||||
def get_repository(state, repo_name):
|
||||
if state is not None:
|
||||
for repository in state:
|
||||
if repository['name'] == repo_name:
|
||||
if repository["name"] == repo_name:
|
||||
return repository
|
||||
return None
|
||||
|
||||
@@ -144,15 +146,19 @@ def get_repository_status(module, command, repository_name):
|
||||
return None
|
||||
elif rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=list_command
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||
rc, out, err
|
||||
),
|
||||
command=list_command,
|
||||
)
|
||||
|
||||
return get_repository(yaml.safe_load(out), repository_name)
|
||||
|
||||
|
||||
# Install repository
|
||||
def install_repository(command, repository_name, repository_url, repository_username, repository_password):
|
||||
def install_repository(
|
||||
command, repository_name, repository_url, repository_username, repository_password
|
||||
):
|
||||
install_command = command + " repo add " + repository_name + " " + repository_url
|
||||
|
||||
if repository_username is not None and repository_password is not None:
|
||||
@@ -174,19 +180,17 @@ def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
repo_name=dict(type='str', aliases=['name'], required=True),
|
||||
repo_url=dict(type='str', aliases=['url']),
|
||||
repo_username=dict(type='str', aliases=['username']),
|
||||
repo_password=dict(type='str', aliases=['password'], no_log=True),
|
||||
repo_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
|
||||
binary_path=dict(type="path"),
|
||||
repo_name=dict(type="str", aliases=["name"], required=True),
|
||||
repo_url=dict(type="str", aliases=["url"]),
|
||||
repo_username=dict(type="str", aliases=["username"]),
|
||||
repo_password=dict(type="str", aliases=["password"], no_log=True),
|
||||
repo_state=dict(
|
||||
default="present", choices=["present", "absent"], aliases=["state"]
|
||||
),
|
||||
),
|
||||
required_together=[
|
||||
['repo_username', 'repo_password']
|
||||
],
|
||||
required_if=[
|
||||
('repo_state', 'present', ['repo_url']),
|
||||
],
|
||||
required_together=[["repo_username", "repo_password"]],
|
||||
required_if=[("repo_state", "present", ["repo_url"])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -195,17 +199,17 @@ def main():
|
||||
|
||||
changed = False
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
repo_name = module.params.get('repo_name')
|
||||
repo_url = module.params.get('repo_url')
|
||||
repo_username = module.params.get('repo_username')
|
||||
repo_password = module.params.get('repo_password')
|
||||
repo_state = module.params.get('repo_state')
|
||||
bin_path = module.params.get("binary_path")
|
||||
repo_name = module.params.get("repo_name")
|
||||
repo_url = module.params.get("repo_url")
|
||||
repo_username = module.params.get("repo_username")
|
||||
repo_password = module.params.get("repo_password")
|
||||
repo_state = module.params.get("repo_state")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd = bin_path
|
||||
else:
|
||||
helm_cmd = module.get_bin_path('helm', required=True)
|
||||
helm_cmd = module.get_bin_path("helm", required=True)
|
||||
|
||||
repository_status = get_repository_status(module, helm_cmd, repo_name)
|
||||
|
||||
@@ -214,10 +218,14 @@ def main():
|
||||
changed = True
|
||||
elif repo_state == "present":
|
||||
if repository_status is None:
|
||||
helm_cmd = install_repository(helm_cmd, repo_name, repo_url, repo_username, repo_password)
|
||||
helm_cmd = install_repository(
|
||||
helm_cmd, repo_name, repo_url, repo_username, repo_password
|
||||
)
|
||||
changed = True
|
||||
elif repository_status['url'] != repo_url:
|
||||
module.fail_json(msg="Repository already have a repository named {0}".format(repo_name))
|
||||
elif repository_status["url"] != repo_url:
|
||||
module.fail_json(
|
||||
msg="Repository already have a repository named {0}".format(repo_name)
|
||||
)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=changed)
|
||||
@@ -227,16 +235,18 @@ def main():
|
||||
rc, out, err = run_helm(module, helm_cmd)
|
||||
|
||||
if repo_password is not None:
|
||||
helm_cmd = helm_cmd.replace(repo_password, '******')
|
||||
helm_cmd = helm_cmd.replace(repo_password, "******")
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=helm_cmd
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||
rc, out, err
|
||||
),
|
||||
command=helm_cmd,
|
||||
)
|
||||
|
||||
module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: helm_template
|
||||
|
||||
@@ -79,9 +79,9 @@ options:
|
||||
- Run C(helm repo update) before the operation. Can be run as part of the template generation or as a separate step.
|
||||
default: false
|
||||
type: bool
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Render templates to specified directory
|
||||
kubernetes.core.helm_template:
|
||||
chart_ref: stable/prometheus
|
||||
@@ -96,9 +96,9 @@ EXAMPLES = r'''
|
||||
copy:
|
||||
dest: myfile.yaml
|
||||
content: "{{ result.stdout }}"
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
stdout:
|
||||
type: str
|
||||
description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents.
|
||||
@@ -114,13 +114,14 @@ command:
|
||||
description: Full C(helm) command run by this module, in case you want to re-run the command outside the module or debug a problem.
|
||||
returned: always
|
||||
sample: helm template --output-dir mychart nginx-stable/nginx-ingress
|
||||
'''
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
IMP_YAML = True
|
||||
except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
@@ -130,8 +131,16 @@ from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
||||
|
||||
|
||||
def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir=None,
|
||||
release_values=None, values_files=None, include_crds=False):
|
||||
def template(
|
||||
cmd,
|
||||
chart_ref,
|
||||
chart_repo_url=None,
|
||||
chart_version=None,
|
||||
output_dir=None,
|
||||
release_values=None,
|
||||
values_files=None,
|
||||
include_crds=False,
|
||||
):
|
||||
cmd += " template " + chart_ref
|
||||
|
||||
if chart_repo_url:
|
||||
@@ -144,8 +153,8 @@ def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir
|
||||
cmd += " --output-dir=" + output_dir
|
||||
|
||||
if release_values:
|
||||
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||
with open(path, 'w') as yaml_file:
|
||||
fd, path = tempfile.mkstemp(suffix=".yml")
|
||||
with open(path, "w") as yaml_file:
|
||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||
cmd += " -f=" + path
|
||||
|
||||
@@ -162,43 +171,49 @@ def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
chart_ref=dict(type='path', required=True),
|
||||
chart_repo_url=dict(type='str'),
|
||||
chart_version=dict(type='str'),
|
||||
include_crds=dict(type='bool', default=False),
|
||||
output_dir=dict(type='path'),
|
||||
release_values=dict(type='dict', default={}, aliases=['values']),
|
||||
values_files=dict(type='list', default=[], elements='str'),
|
||||
update_repo_cache=dict(type='bool', default=False)
|
||||
binary_path=dict(type="path"),
|
||||
chart_ref=dict(type="path", required=True),
|
||||
chart_repo_url=dict(type="str"),
|
||||
chart_version=dict(type="str"),
|
||||
include_crds=dict(type="bool", default=False),
|
||||
output_dir=dict(type="path"),
|
||||
release_values=dict(type="dict", default={}, aliases=["values"]),
|
||||
values_files=dict(type="list", default=[], elements="str"),
|
||||
update_repo_cache=dict(type="bool", default=False),
|
||||
),
|
||||
supports_check_mode=True
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
check_mode = module.check_mode
|
||||
bin_path = module.params.get('binary_path')
|
||||
chart_ref = module.params.get('chart_ref')
|
||||
chart_repo_url = module.params.get('chart_repo_url')
|
||||
chart_version = module.params.get('chart_version')
|
||||
include_crds = module.params.get('include_crds')
|
||||
output_dir = module.params.get('output_dir')
|
||||
release_values = module.params.get('release_values')
|
||||
values_files = module.params.get('values_files')
|
||||
update_repo_cache = module.params.get('update_repo_cache')
|
||||
bin_path = module.params.get("binary_path")
|
||||
chart_ref = module.params.get("chart_ref")
|
||||
chart_repo_url = module.params.get("chart_repo_url")
|
||||
chart_version = module.params.get("chart_version")
|
||||
include_crds = module.params.get("include_crds")
|
||||
output_dir = module.params.get("output_dir")
|
||||
release_values = module.params.get("release_values")
|
||||
values_files = module.params.get("values_files")
|
||||
update_repo_cache = module.params.get("update_repo_cache")
|
||||
|
||||
if not IMP_YAML:
|
||||
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
||||
|
||||
helm_cmd = bin_path or module.get_bin_path('helm', required=True)
|
||||
helm_cmd = bin_path or module.get_bin_path("helm", required=True)
|
||||
|
||||
if update_repo_cache:
|
||||
update_cmd = helm_cmd + " repo update"
|
||||
run_helm(module, update_cmd)
|
||||
|
||||
tmpl_cmd = template(helm_cmd, chart_ref, chart_repo_url=chart_repo_url,
|
||||
chart_version=chart_version, output_dir=output_dir,
|
||||
release_values=release_values, values_files=values_files,
|
||||
include_crds=include_crds)
|
||||
tmpl_cmd = template(
|
||||
helm_cmd,
|
||||
chart_ref,
|
||||
chart_repo_url=chart_repo_url,
|
||||
chart_version=chart_version,
|
||||
output_dir=output_dir,
|
||||
release_values=release_values,
|
||||
values_files=values_files,
|
||||
include_crds=include_crds,
|
||||
)
|
||||
|
||||
if not check_mode:
|
||||
rc, out, err = run_helm(module, tmpl_cmd)
|
||||
@@ -207,14 +222,9 @@ def main():
|
||||
rc = 0
|
||||
|
||||
module.exit_json(
|
||||
failed=False,
|
||||
changed=True,
|
||||
command=tmpl_cmd,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
failed=False, changed=True, command=tmpl_cmd, stdout=out, stderr=err, rc=rc
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s
|
||||
|
||||
@@ -158,9 +158,9 @@ requirements:
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
- "jsonpatch"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Create a k8s namespace
|
||||
kubernetes.core.k8s:
|
||||
name: testing
|
||||
@@ -302,9 +302,9 @@ EXAMPLES = r'''
|
||||
- name: py
|
||||
image: python:3.7-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
|
||||
@@ -344,20 +344,27 @@ result:
|
||||
description: error while trying to create/delete the object.
|
||||
returned: error
|
||||
type: complex
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
|
||||
AUTH_ARG_SPEC,
|
||||
WAIT_ARG_SPEC,
|
||||
NAME_ARG_SPEC,
|
||||
RESOURCE_ARG_SPEC,
|
||||
DELETE_OPTS_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
def validate_spec():
|
||||
return dict(
|
||||
fail_on_error=dict(type='bool'),
|
||||
fail_on_error=dict(type="bool"),
|
||||
version=dict(),
|
||||
strict=dict(type='bool', default=True)
|
||||
strict=dict(type="bool", default=True),
|
||||
)
|
||||
|
||||
|
||||
@@ -366,17 +373,23 @@ def argspec():
|
||||
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
|
||||
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
|
||||
argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
|
||||
argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
|
||||
argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec())
|
||||
argument_spec['append_hash'] = dict(type='bool', default=False)
|
||||
argument_spec['apply'] = dict(type='bool', default=False)
|
||||
argument_spec['template'] = dict(type='raw', default=None)
|
||||
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
|
||||
argument_spec['continue_on_error'] = dict(type='bool', default=False)
|
||||
argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched'])
|
||||
argument_spec['force'] = dict(type='bool', default=False)
|
||||
argument_spec['label_selectors'] = dict(type='list', elements='str')
|
||||
argument_spec['generate_name'] = dict()
|
||||
argument_spec["merge_type"] = dict(
|
||||
type="list", elements="str", choices=["json", "merge", "strategic-merge"]
|
||||
)
|
||||
argument_spec["validate"] = dict(type="dict", default=None, options=validate_spec())
|
||||
argument_spec["append_hash"] = dict(type="bool", default=False)
|
||||
argument_spec["apply"] = dict(type="bool", default=False)
|
||||
argument_spec["template"] = dict(type="raw", default=None)
|
||||
argument_spec["delete_options"] = dict(
|
||||
type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)
|
||||
)
|
||||
argument_spec["continue_on_error"] = dict(type="bool", default=False)
|
||||
argument_spec["state"] = dict(
|
||||
default="present", choices=["present", "absent", "patched"]
|
||||
)
|
||||
argument_spec["force"] = dict(type="bool", default=False)
|
||||
argument_spec["label_selectors"] = dict(type="list", elements="str")
|
||||
argument_spec["generate_name"] = dict()
|
||||
|
||||
return argument_spec
|
||||
|
||||
@@ -392,11 +405,11 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn
|
||||
k8s_ansible_mixin.warnings = []
|
||||
|
||||
k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind')
|
||||
k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get('api_version')
|
||||
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get('name')
|
||||
k8s_ansible_mixin.generate_name = k8s_ansible_mixin.params.get('generate_name')
|
||||
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
|
||||
k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get("kind")
|
||||
k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get("api_version")
|
||||
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get("name")
|
||||
k8s_ansible_mixin.generate_name = k8s_ansible_mixin.params.get("generate_name")
|
||||
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get("namespace")
|
||||
|
||||
k8s_ansible_mixin.check_library_version()
|
||||
k8s_ansible_mixin.set_resource_definitions(module)
|
||||
@@ -405,20 +418,26 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
|
||||
def main():
|
||||
mutually_exclusive = [
|
||||
('resource_definition', 'src'),
|
||||
('merge_type', 'apply'),
|
||||
('template', 'resource_definition'),
|
||||
('template', 'src'),
|
||||
('name', 'generate_name'),
|
||||
("resource_definition", "src"),
|
||||
("merge_type", "apply"),
|
||||
("template", "resource_definition"),
|
||||
("template", "src"),
|
||||
("name", "generate_name"),
|
||||
]
|
||||
module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argspec(),
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
module: k8s_cluster_info
|
||||
|
||||
version_added: "0.11.1"
|
||||
@@ -36,9 +37,9 @@ requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Get Cluster information
|
||||
kubernetes.core.k8s_cluster_info:
|
||||
register: api_status
|
||||
@@ -47,9 +48,9 @@ EXAMPLES = r'''
|
||||
kubernetes.core.k8s_cluster_info:
|
||||
invalidate_cache: False
|
||||
register: api_status
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
connection:
|
||||
description:
|
||||
- Connection information
|
||||
@@ -136,7 +137,7 @@ apis:
|
||||
description: Resource singular name
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
@@ -145,7 +146,10 @@ from collections import defaultdict
|
||||
|
||||
HAS_K8S = False
|
||||
try:
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
||||
ResourceList,
|
||||
)
|
||||
|
||||
HAS_K8S = True
|
||||
except ImportError as e:
|
||||
K8S_IMP_ERR = e
|
||||
@@ -154,12 +158,18 @@ except ImportError as e:
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
def execute_module(module, client):
|
||||
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
|
||||
invalidate_cache = boolean(
|
||||
module.params.get("invalidate_cache", True), strict=False
|
||||
)
|
||||
if invalidate_cache:
|
||||
client.resources.invalidate_cache()
|
||||
results = defaultdict(dict)
|
||||
@@ -167,47 +177,60 @@ def execute_module(module, client):
|
||||
resource = resource[0]
|
||||
if isinstance(resource, ResourceList):
|
||||
continue
|
||||
key = resource.group_version if resource.group == '' else '/'.join([resource.group, resource.group_version.split('/')[-1]])
|
||||
key = (
|
||||
resource.group_version
|
||||
if resource.group == ""
|
||||
else "/".join([resource.group, resource.group_version.split("/")[-1]])
|
||||
)
|
||||
results[key][resource.kind] = {
|
||||
'categories': resource.categories if resource.categories else [],
|
||||
'name': resource.name,
|
||||
'namespaced': resource.namespaced,
|
||||
'preferred': resource.preferred,
|
||||
'short_names': resource.short_names if resource.short_names else [],
|
||||
'singular_name': resource.singular_name,
|
||||
"categories": resource.categories if resource.categories else [],
|
||||
"name": resource.name,
|
||||
"namespaced": resource.namespaced,
|
||||
"preferred": resource.preferred,
|
||||
"short_names": resource.short_names if resource.short_names else [],
|
||||
"singular_name": resource.singular_name,
|
||||
}
|
||||
configuration = client.configuration
|
||||
connection = {
|
||||
'cert_file': configuration.cert_file,
|
||||
'host': configuration.host,
|
||||
'password': configuration.password,
|
||||
'proxy': configuration.proxy,
|
||||
'ssl_ca_cert': configuration.ssl_ca_cert,
|
||||
'username': configuration.username,
|
||||
'verify_ssl': configuration.verify_ssl,
|
||||
"cert_file": configuration.cert_file,
|
||||
"host": configuration.host,
|
||||
"password": configuration.password,
|
||||
"proxy": configuration.proxy,
|
||||
"ssl_ca_cert": configuration.ssl_ca_cert,
|
||||
"username": configuration.username,
|
||||
"verify_ssl": configuration.verify_ssl,
|
||||
}
|
||||
from kubernetes import __version__ as version
|
||||
|
||||
version_info = {
|
||||
'client': version,
|
||||
'server': client.version,
|
||||
"client": version,
|
||||
"server": client.version,
|
||||
}
|
||||
module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
|
||||
module.exit_json(
|
||||
changed=False, apis=results, connection=connection, version=version_info
|
||||
)
|
||||
|
||||
|
||||
def argspec():
|
||||
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
spec['invalidate_cache'] = dict(type='bool', default=True)
|
||||
spec["invalidate_cache"] = dict(type="bool", default=True)
|
||||
return spec
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||
if not HAS_K8S:
|
||||
module.fail_json(msg=missing_required_lib('kubernetes'), exception=K8S_IMP_EXC,
|
||||
error=to_native(K8S_IMP_ERR))
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import get_api_client
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("kubernetes"),
|
||||
exception=K8S_IMP_EXC,
|
||||
error=to_native(K8S_IMP_ERR),
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
execute_module(module, client=get_api_client(module=module))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s_cp
|
||||
|
||||
@@ -78,9 +79,9 @@ options:
|
||||
|
||||
notes:
|
||||
- the tar binary is required on the container when copying from local filesystem to pod.
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
|
||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
|
||||
kubernetes.core.k8s_cp:
|
||||
@@ -125,16 +126,16 @@ EXAMPLES = r'''
|
||||
pod: some-pod
|
||||
remote_path: /tmp/foo.txt
|
||||
content: "This content will be copied into remote file"
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- message describing the copy operation successfully done.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
import os
|
||||
@@ -146,13 +147,23 @@ import tarfile
|
||||
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes.stream import stream
|
||||
from kubernetes.stream.ws_client import STDOUT_CHANNEL, STDERR_CHANNEL, ERROR_CHANNEL, ABNF
|
||||
from kubernetes.stream.ws_client import (
|
||||
STDOUT_CHANNEL,
|
||||
STDERR_CHANNEL,
|
||||
ERROR_CHANNEL,
|
||||
ABNF,
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -164,22 +175,21 @@ except ImportError:
|
||||
|
||||
|
||||
class K8SCopy(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, module, client):
|
||||
self.client = client
|
||||
self.module = module
|
||||
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
||||
|
||||
self.local_path = module.params.get('local_path')
|
||||
self.name = module.params.get('pod')
|
||||
self.namespace = module.params.get('namespace')
|
||||
self.remote_path = module.params.get('remote_path')
|
||||
self.content = module.params.get('content')
|
||||
self.local_path = module.params.get("local_path")
|
||||
self.name = module.params.get("pod")
|
||||
self.namespace = module.params.get("namespace")
|
||||
self.remote_path = module.params.get("remote_path")
|
||||
self.content = module.params.get("content")
|
||||
|
||||
self.no_preserve = module.params.get('no_preserve')
|
||||
self.no_preserve = module.params.get("no_preserve")
|
||||
self.container_arg = {}
|
||||
if module.params.get('container'):
|
||||
self.container_arg['container'] = module.params.get('container')
|
||||
if module.params.get("container"):
|
||||
self.container_arg["container"] = module.params.get("container")
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
@@ -190,6 +200,7 @@ class K8SCopyFromPod(K8SCopy):
|
||||
"""
|
||||
Copy files/directory from Pod into local filesystem
|
||||
"""
|
||||
|
||||
def __init__(self, module, client):
|
||||
super(K8SCopyFromPod, self).__init__(module, client)
|
||||
self.is_remote_path_dir = None
|
||||
@@ -201,31 +212,48 @@ class K8SCopyFromPod(K8SCopy):
|
||||
if it is a directory the file list will be updated accordingly
|
||||
"""
|
||||
try:
|
||||
find_cmd = ['find', self.remote_path, '-type', 'f', '-name', '*']
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=find_cmd,
|
||||
stdout=True, stderr=True,
|
||||
stdin=False, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
find_cmd = ["find", self.remote_path, "-type", "f", "-name", "*"]
|
||||
response = stream(
|
||||
self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=find_cmd,
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
_preload_content=False,
|
||||
**self.container_arg
|
||||
)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to execute on pod {0}/{1} due to : {2}".format(self.namespace, self.name, to_native(e)))
|
||||
self.module.fail_json(
|
||||
msg="Failed to execute on pod {0}/{1} due to : {2}".format(
|
||||
self.namespace, self.name, to_native(e)
|
||||
)
|
||||
)
|
||||
stderr = []
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
if response.peek_stdout():
|
||||
self.files_to_copy.extend(response.read_stdout().rstrip('\n').split('\n'))
|
||||
self.files_to_copy.extend(
|
||||
response.read_stdout().rstrip("\n").split("\n")
|
||||
)
|
||||
if response.peek_stderr():
|
||||
err = response.read_stderr()
|
||||
if "No such file or directory" in err:
|
||||
self.module.fail_json(msg="{0} does not exist in remote pod filesystem".format(self.remote_path))
|
||||
self.module.fail_json(
|
||||
msg="{0} does not exist in remote pod filesystem".format(
|
||||
self.remote_path
|
||||
)
|
||||
)
|
||||
stderr.append(err)
|
||||
error = response.read_channel(ERROR_CHANNEL)
|
||||
response.close()
|
||||
error = yaml.safe_load(error)
|
||||
if error['status'] != 'Success':
|
||||
self.module.fail_json(msg="Failed to execute on Pod due to: {0}".format(error))
|
||||
if error["status"] != "Success":
|
||||
self.module.fail_json(
|
||||
msg="Failed to execute on Pod due to: {0}".format(error)
|
||||
)
|
||||
|
||||
def read(self):
|
||||
self.stdout = None
|
||||
@@ -235,12 +263,15 @@ class K8SCopyFromPod(K8SCopy):
|
||||
if not self.response.sock.connected:
|
||||
self.response._connected = False
|
||||
else:
|
||||
ret, out, err = select((self.response.sock.sock, ), (), (), 0)
|
||||
ret, out, err = select((self.response.sock.sock,), (), (), 0)
|
||||
if ret:
|
||||
code, frame = self.response.sock.recv_data_frame(True)
|
||||
if code == ABNF.OPCODE_CLOSE:
|
||||
self.response._connected = False
|
||||
elif code in (ABNF.OPCODE_BINARY, ABNF.OPCODE_TEXT) and len(frame.data) > 1:
|
||||
elif (
|
||||
code in (ABNF.OPCODE_BINARY, ABNF.OPCODE_TEXT)
|
||||
and len(frame.data) > 1
|
||||
):
|
||||
channel = frame.data[0]
|
||||
content = frame.data[1:]
|
||||
if content:
|
||||
@@ -250,7 +281,9 @@ class K8SCopyFromPod(K8SCopy):
|
||||
self.stderr = content.decode("utf-8", "replace")
|
||||
|
||||
def copy(self):
|
||||
is_remote_path_dir = len(self.files_to_copy) > 1 or self.files_to_copy[0] != self.remote_path
|
||||
is_remote_path_dir = (
|
||||
len(self.files_to_copy) > 1 or self.files_to_copy[0] != self.remote_path
|
||||
)
|
||||
relpath_start = self.remote_path
|
||||
if is_remote_path_dir and os.path.isdir(self.local_path):
|
||||
relpath_start = os.path.dirname(self.remote_path)
|
||||
@@ -258,20 +291,27 @@ class K8SCopyFromPod(K8SCopy):
|
||||
for remote_file in self.files_to_copy:
|
||||
dest_file = self.local_path
|
||||
if is_remote_path_dir:
|
||||
dest_file = os.path.join(self.local_path, os.path.relpath(remote_file, start=relpath_start))
|
||||
dest_file = os.path.join(
|
||||
self.local_path, os.path.relpath(remote_file, start=relpath_start)
|
||||
)
|
||||
# create directory to copy file in
|
||||
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
|
||||
|
||||
pod_command = ['cat', remote_file]
|
||||
self.response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stderr=True, stdin=True,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
pod_command = ["cat", remote_file]
|
||||
self.response = stream(
|
||||
self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stderr=True,
|
||||
stdin=True,
|
||||
stdout=True,
|
||||
tty=False,
|
||||
_preload_content=False,
|
||||
**self.container_arg
|
||||
)
|
||||
errors = []
|
||||
with open(dest_file, 'wb') as fh:
|
||||
with open(dest_file, "wb") as fh:
|
||||
while self.response._connected:
|
||||
self.read()
|
||||
if self.stdout:
|
||||
@@ -279,35 +319,57 @@ class K8SCopyFromPod(K8SCopy):
|
||||
if self.stderr:
|
||||
errors.append(self.stderr)
|
||||
if errors:
|
||||
self.module.fail_json(msg="Failed to copy file from Pod: {0}".format(''.join(errors)))
|
||||
self.module.exit_json(changed=True, result="{0} successfully copied locally into {1}".format(self.remote_path, self.local_path))
|
||||
self.module.fail_json(
|
||||
msg="Failed to copy file from Pod: {0}".format("".join(errors))
|
||||
)
|
||||
self.module.exit_json(
|
||||
changed=True,
|
||||
result="{0} successfully copied locally into {1}".format(
|
||||
self.remote_path, self.local_path
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.list_remote_files()
|
||||
if self.files_to_copy == []:
|
||||
self.module.exit_json(changed=False, warning="No file found from directory '{0}' into remote Pod.".format(self.remote_path))
|
||||
self.module.exit_json(
|
||||
changed=False,
|
||||
warning="No file found from directory '{0}' into remote Pod.".format(
|
||||
self.remote_path
|
||||
),
|
||||
)
|
||||
self.copy()
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to copy file/directory from Pod due to: {0}".format(to_native(e)))
|
||||
self.module.fail_json(
|
||||
msg="Failed to copy file/directory from Pod due to: {0}".format(
|
||||
to_native(e)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class K8SCopyToPod(K8SCopy):
|
||||
"""
|
||||
Copy files/directory from local filesystem into remote Pod
|
||||
"""
|
||||
|
||||
def __init__(self, module, client):
|
||||
super(K8SCopyToPod, self).__init__(module, client)
|
||||
self.files_to_copy = list()
|
||||
|
||||
def run_from_pod(self, command):
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=command,
|
||||
stderr=True, stdin=False,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
response = stream(
|
||||
self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=command,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
stdout=True,
|
||||
tty=False,
|
||||
_preload_content=False,
|
||||
**self.container_arg
|
||||
)
|
||||
errors = []
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
@@ -317,24 +379,31 @@ class K8SCopyToPod(K8SCopy):
|
||||
err = response.read_channel(ERROR_CHANNEL)
|
||||
err = yaml.safe_load(err)
|
||||
response.close()
|
||||
if err['status'] != 'Success':
|
||||
self.module.fail_json(msg="Failed to run {0} on Pod.".format(command), errors=errors)
|
||||
if err["status"] != "Success":
|
||||
self.module.fail_json(
|
||||
msg="Failed to run {0} on Pod.".format(command), errors=errors
|
||||
)
|
||||
|
||||
def is_remote_path_dir(self):
|
||||
pod_command = ['test', '-d', self.remote_path]
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stdout=True, stderr=True,
|
||||
stdin=False, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
pod_command = ["test", "-d", self.remote_path]
|
||||
response = stream(
|
||||
self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
_preload_content=False,
|
||||
**self.container_arg
|
||||
)
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
err = response.read_channel(ERROR_CHANNEL)
|
||||
err = yaml.safe_load(err)
|
||||
response.close()
|
||||
if err['status'] == 'Success':
|
||||
if err["status"] == "Success":
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -355,33 +424,52 @@ class K8SCopyToPod(K8SCopy):
|
||||
src_file = self.named_temp_file.name
|
||||
else:
|
||||
if not os.path.exists(self.local_path):
|
||||
self.module.fail_json(msg="{0} does not exist in local filesystem".format(self.local_path))
|
||||
self.module.fail_json(
|
||||
msg="{0} does not exist in local filesystem".format(
|
||||
self.local_path
|
||||
)
|
||||
)
|
||||
if not os.access(self.local_path, os.R_OK):
|
||||
self.module.fail_json(msg="{0} not readable".format(self.local_path))
|
||||
self.module.fail_json(
|
||||
msg="{0} not readable".format(self.local_path)
|
||||
)
|
||||
|
||||
if self.is_remote_path_dir():
|
||||
if self.content:
|
||||
self.module.fail_json(msg="When content is specified, remote path should not be an existing directory")
|
||||
self.module.fail_json(
|
||||
msg="When content is specified, remote path should not be an existing directory"
|
||||
)
|
||||
else:
|
||||
dest_file = os.path.join(dest_file, os.path.basename(src_file))
|
||||
|
||||
if self.no_preserve:
|
||||
tar_command = ['tar', '--no-same-permissions', '--no-same-owner', '-xmf', '-']
|
||||
tar_command = [
|
||||
"tar",
|
||||
"--no-same-permissions",
|
||||
"--no-same-owner",
|
||||
"-xmf",
|
||||
"-",
|
||||
]
|
||||
else:
|
||||
tar_command = ['tar', '-xmf', '-']
|
||||
tar_command = ["tar", "-xmf", "-"]
|
||||
|
||||
if dest_file.startswith("/"):
|
||||
tar_command.extend(['-C', '/'])
|
||||
tar_command.extend(["-C", "/"])
|
||||
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=tar_command,
|
||||
stderr=True, stdin=True,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
response = stream(
|
||||
self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=tar_command,
|
||||
stderr=True,
|
||||
stdin=True,
|
||||
stdout=True,
|
||||
tty=False,
|
||||
_preload_content=False,
|
||||
**self.container_arg
|
||||
)
|
||||
with TemporaryFile() as tar_buffer:
|
||||
with tarfile.open(fileobj=tar_buffer, mode='w') as tar:
|
||||
with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
|
||||
tar.add(src_file, dest_file)
|
||||
tar_buffer.seek(0)
|
||||
commands = []
|
||||
@@ -407,36 +495,59 @@ class K8SCopyToPod(K8SCopy):
|
||||
response.close()
|
||||
if stderr:
|
||||
self.close_temp_file()
|
||||
self.module.fail_json(command=tar_command, msg="Failed to copy local file/directory into Pod due to: {0}".format(''.join(stderr)))
|
||||
self.module.fail_json(
|
||||
command=tar_command,
|
||||
msg="Failed to copy local file/directory into Pod due to: {0}".format(
|
||||
"".join(stderr)
|
||||
),
|
||||
)
|
||||
self.close_temp_file()
|
||||
if self.content:
|
||||
self.module.exit_json(changed=True, result="Content successfully copied into {0} on remote Pod".format(self.remote_path))
|
||||
self.module.exit_json(changed=True, result="{0} successfully copied into remote Pod into {1}".format(self.local_path, self.remote_path))
|
||||
self.module.exit_json(
|
||||
changed=True,
|
||||
result="Content successfully copied into {0} on remote Pod".format(
|
||||
self.remote_path
|
||||
),
|
||||
)
|
||||
self.module.exit_json(
|
||||
changed=True,
|
||||
result="{0} successfully copied into remote Pod into {1}".format(
|
||||
self.local_path, self.remote_path
|
||||
),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to copy local file/directory into Pod due to: {0}".format(to_native(e)))
|
||||
self.module.fail_json(
|
||||
msg="Failed to copy local file/directory into Pod due to: {0}".format(
|
||||
to_native(e)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def check_pod(k8s_ansible_mixin, module):
|
||||
resource = k8s_ansible_mixin.find_resource("Pod", None, True)
|
||||
namespace = module.params.get('namespace')
|
||||
name = module.params.get('pod')
|
||||
container = module.params.get('container')
|
||||
namespace = module.params.get("namespace")
|
||||
name = module.params.get("pod")
|
||||
container = module.params.get("container")
|
||||
|
||||
def _fail(exc):
|
||||
arg = {}
|
||||
if hasattr(exc, 'body'):
|
||||
msg = "Namespace={0} Kind=Pod Name={1}: Failed requested object: {2}".format(namespace, name, exc.body)
|
||||
if hasattr(exc, "body"):
|
||||
msg = "Namespace={0} Kind=Pod Name={1}: Failed requested object: {2}".format(
|
||||
namespace, name, exc.body
|
||||
)
|
||||
else:
|
||||
msg = to_native(exc)
|
||||
for attr in ['status', 'reason']:
|
||||
for attr in ["status", "reason"]:
|
||||
if hasattr(exc, attr):
|
||||
arg[attr] = getattr(exc, attr)
|
||||
module.fail_json(msg=msg, **arg)
|
||||
|
||||
try:
|
||||
result = resource.get(name=name, namespace=namespace)
|
||||
containers = [c['name'] for c in result.to_dict()['status']['containerStatuses']]
|
||||
containers = [
|
||||
c["name"] for c in result.to_dict()["status"]["containerStatuses"]
|
||||
]
|
||||
if container and container not in containers:
|
||||
module.fail_json(msg="Pod has no container {0}".format(container))
|
||||
return containers
|
||||
@@ -457,12 +568,14 @@ def execute_module(module):
|
||||
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
containers = check_pod(k8s_ansible_mixin, module)
|
||||
if len(containers) > 1 and module.params.get('container') is None:
|
||||
module.fail_json(msg="Pod contains more than 1 container, option 'container' should be set")
|
||||
if len(containers) > 1 and module.params.get("container") is None:
|
||||
module.fail_json(
|
||||
msg="Pod contains more than 1 container, option 'container' should be set"
|
||||
)
|
||||
|
||||
try:
|
||||
load_class = {'to_pod': K8SCopyToPod, 'from_pod': K8SCopyFromPod}
|
||||
state = module.params.get('state')
|
||||
load_class = {"to_pod": K8SCopyToPod, "from_pod": K8SCopyFromPod}
|
||||
state = module.params.get("state")
|
||||
k8s_copy = load_class.get(state)(module, k8s_ansible_mixin.client)
|
||||
k8s_copy.run()
|
||||
except Exception as e:
|
||||
@@ -471,23 +584,29 @@ def execute_module(module):
|
||||
|
||||
def main():
|
||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
argument_spec['namespace'] = {'type': 'str', 'required': True}
|
||||
argument_spec['pod'] = {'type': 'str', 'required': True}
|
||||
argument_spec['container'] = {}
|
||||
argument_spec['remote_path'] = {'type': 'path', 'required': True}
|
||||
argument_spec['local_path'] = {'type': 'path'}
|
||||
argument_spec['content'] = {'type': 'str'}
|
||||
argument_spec['state'] = {'type': 'str', 'default': 'to_pod', 'choices': ['to_pod', 'from_pod']}
|
||||
argument_spec['no_preserve'] = {'type': 'bool', 'default': False}
|
||||
argument_spec["namespace"] = {"type": "str", "required": True}
|
||||
argument_spec["pod"] = {"type": "str", "required": True}
|
||||
argument_spec["container"] = {}
|
||||
argument_spec["remote_path"] = {"type": "path", "required": True}
|
||||
argument_spec["local_path"] = {"type": "path"}
|
||||
argument_spec["content"] = {"type": "str"}
|
||||
argument_spec["state"] = {
|
||||
"type": "str",
|
||||
"default": "to_pod",
|
||||
"choices": ["to_pod", "from_pod"],
|
||||
}
|
||||
argument_spec["no_preserve"] = {"type": "bool", "default": False}
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=[('local_path', 'content')],
|
||||
required_if=[('state', 'from_pod', ['local_path'])],
|
||||
required_one_of=[['local_path', 'content']],
|
||||
supports_check_mode=True)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=[("local_path", "content")],
|
||||
required_if=[("state", "from_pod", ["local_path"])],
|
||||
required_one_of=[["local_path", "content"]],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
execute_module(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s_drain
|
||||
|
||||
@@ -83,9 +83,9 @@ options:
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: drain
|
||||
@@ -109,20 +109,24 @@ EXAMPLES = r'''
|
||||
state: cordon
|
||||
name: foo
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- The node status and the number of pods deleted.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
import copy
|
||||
from datetime import datetime
|
||||
import time
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
@@ -144,7 +148,7 @@ def filter_pods(pods, force, ignore_daemonset):
|
||||
continue
|
||||
|
||||
# Any finished pod can be deleted
|
||||
if pod.status.phase in ('Succeeded', 'Failed'):
|
||||
if pod.status.phase in ("Succeeded", "Failed"):
|
||||
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
||||
continue
|
||||
|
||||
@@ -167,19 +171,29 @@ def filter_pods(pods, force, ignore_daemonset):
|
||||
|
||||
warnings, errors = [], []
|
||||
if unmanaged:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in unmanaged])
|
||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in unmanaged])
|
||||
if not force:
|
||||
errors.append("cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
||||
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(pod_names))
|
||||
errors.append(
|
||||
"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
||||
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(
|
||||
pod_names
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Pod not managed will be deleted as 'force' is true
|
||||
warnings.append("Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(pod_names))
|
||||
warnings.append(
|
||||
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
|
||||
pod_names
|
||||
)
|
||||
)
|
||||
to_delete += unmanaged
|
||||
|
||||
# mirror pods warning
|
||||
if mirror:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in mirror])
|
||||
warnings.append("cannot delete mirror Pods using API server: {0}.".format(pod_names))
|
||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in mirror])
|
||||
warnings.append(
|
||||
"cannot delete mirror Pods using API server: {0}.".format(pod_names)
|
||||
)
|
||||
|
||||
# local storage
|
||||
if localStorage:
|
||||
@@ -187,19 +201,24 @@ def filter_pods(pods, force, ignore_daemonset):
|
||||
|
||||
# DaemonSet managed Pods
|
||||
if daemonSet:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in daemonSet])
|
||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in daemonSet])
|
||||
if not ignore_daemonset:
|
||||
errors.append("cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(pod_names))
|
||||
errors.append(
|
||||
"cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(
|
||||
pod_names
|
||||
)
|
||||
)
|
||||
else:
|
||||
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
||||
return to_delete, warnings, errors
|
||||
|
||||
|
||||
class K8sDrainAnsible(object):
|
||||
|
||||
def __init__(self, module):
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
self._module = module
|
||||
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
@@ -215,17 +234,25 @@ class K8sDrainAnsible(object):
|
||||
self._k8s_ansible_mixin.warn = self._module.warn
|
||||
self._k8s_ansible_mixin.warnings = []
|
||||
|
||||
self._api_instance = core_v1_api.CoreV1Api(self._k8s_ansible_mixin.client.client)
|
||||
self._api_instance = core_v1_api.CoreV1Api(
|
||||
self._k8s_ansible_mixin.client.client
|
||||
)
|
||||
self._k8s_ansible_mixin.check_library_version()
|
||||
|
||||
# delete options
|
||||
self._drain_options = module.params.get('delete_options', {})
|
||||
self._drain_options = module.params.get("delete_options", {})
|
||||
self._delete_options = None
|
||||
if self._drain_options.get('terminate_grace_period'):
|
||||
if self._drain_options.get("terminate_grace_period"):
|
||||
self._delete_options = {}
|
||||
self._delete_options.update({'apiVersion': 'v1'})
|
||||
self._delete_options.update({'kind': 'DeleteOptions'})
|
||||
self._delete_options.update({'gracePeriodSeconds': self._drain_options.get('terminate_grace_period')})
|
||||
self._delete_options.update({"apiVersion": "v1"})
|
||||
self._delete_options.update({"kind": "DeleteOptions"})
|
||||
self._delete_options.update(
|
||||
{
|
||||
"gracePeriodSeconds": self._drain_options.get(
|
||||
"terminate_grace_period"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
self._changed = False
|
||||
|
||||
@@ -241,13 +268,17 @@ class K8sDrainAnsible(object):
|
||||
if not pod:
|
||||
pod = pods.pop()
|
||||
try:
|
||||
response = self._api_instance.read_namespaced_pod(namespace=pod[0], name=pod[1])
|
||||
response = self._api_instance.read_namespaced_pod(
|
||||
namespace=pod[0], name=pod[1]
|
||||
)
|
||||
if not response:
|
||||
pod = None
|
||||
time.sleep(wait_sleep)
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
self._module.fail_json(msg="Exception raised: {0}".format(exc.reason))
|
||||
self._module.fail_json(
|
||||
msg="Exception raised: {0}".format(exc.reason)
|
||||
)
|
||||
pod = None
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Exception raised: {0}".format(to_native(e)))
|
||||
@@ -257,37 +288,50 @@ class K8sDrainAnsible(object):
|
||||
|
||||
def evict_pods(self, pods):
|
||||
for namespace, name in pods:
|
||||
definition = {
|
||||
'metadata': {
|
||||
'name': name,
|
||||
'namespace': namespace
|
||||
}
|
||||
}
|
||||
definition = {"metadata": {"name": name, "namespace": namespace}}
|
||||
if self._delete_options:
|
||||
definition.update({'delete_options': self._delete_options})
|
||||
definition.update({"delete_options": self._delete_options})
|
||||
try:
|
||||
if self._drain_options.get('disable_eviction'):
|
||||
if self._drain_options.get("disable_eviction"):
|
||||
body = V1DeleteOptions(**definition)
|
||||
self._api_instance.delete_namespaced_pod(name=name, namespace=namespace, body=body)
|
||||
self._api_instance.delete_namespaced_pod(
|
||||
name=name, namespace=namespace, body=body
|
||||
)
|
||||
else:
|
||||
body = V1beta1Eviction(**definition)
|
||||
self._api_instance.create_namespaced_pod_eviction(name=name, namespace=namespace, body=body)
|
||||
self._api_instance.create_namespaced_pod_eviction(
|
||||
name=name, namespace=namespace, body=body
|
||||
)
|
||||
self._changed = True
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, exc.reason))
|
||||
self._module.fail_json(
|
||||
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
||||
namespace, name, exc.reason
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, to_native(exc)))
|
||||
self._module.fail_json(
|
||||
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
||||
namespace, name, to_native(exc)
|
||||
)
|
||||
)
|
||||
|
||||
def delete_or_evict_pods(self, node_unschedulable):
|
||||
# Mark node as unschedulable
|
||||
result = []
|
||||
if not node_unschedulable:
|
||||
self.patch_node(unschedulable=True)
|
||||
result.append("node {0} marked unschedulable.".format(self._module.params.get('name')))
|
||||
result.append(
|
||||
"node {0} marked unschedulable.".format(self._module.params.get("name"))
|
||||
)
|
||||
self._changed = True
|
||||
else:
|
||||
result.append("node {0} already marked unschedulable.".format(self._module.params.get('name')))
|
||||
result.append(
|
||||
"node {0} already marked unschedulable.".format(
|
||||
self._module.params.get("name")
|
||||
)
|
||||
)
|
||||
|
||||
def _revert_node_patch():
|
||||
if self._changed:
|
||||
@@ -295,77 +339,109 @@ class K8sDrainAnsible(object):
|
||||
self.patch_node(unschedulable=False)
|
||||
|
||||
try:
|
||||
field_selector = "spec.nodeName={name}".format(name=self._module.params.get('name'))
|
||||
pod_list = self._api_instance.list_pod_for_all_namespaces(field_selector=field_selector)
|
||||
field_selector = "spec.nodeName={name}".format(
|
||||
name=self._module.params.get("name")
|
||||
)
|
||||
pod_list = self._api_instance.list_pod_for_all_namespaces(
|
||||
field_selector=field_selector
|
||||
)
|
||||
# Filter pods
|
||||
force = self._drain_options.get('force', False)
|
||||
ignore_daemonset = self._drain_options.get('ignore_daemonsets', False)
|
||||
pods, warnings, errors = filter_pods(pod_list.items, force, ignore_daemonset)
|
||||
force = self._drain_options.get("force", False)
|
||||
ignore_daemonset = self._drain_options.get("ignore_daemonsets", False)
|
||||
pods, warnings, errors = filter_pods(
|
||||
pod_list.items, force, ignore_daemonset
|
||||
)
|
||||
if errors:
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Pod deletion errors: {0}".format(" ".join(errors)))
|
||||
self._module.fail_json(
|
||||
msg="Pod deletion errors: {0}".format(" ".join(errors))
|
||||
)
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Failed to list pod from node {name} due to: {reason}".format(
|
||||
name=self._module.params.get('name'), reason=exc.reason), status=exc.status)
|
||||
self._module.fail_json(
|
||||
msg="Failed to list pod from node {name} due to: {reason}".format(
|
||||
name=self._module.params.get("name"), reason=exc.reason
|
||||
),
|
||||
status=exc.status,
|
||||
)
|
||||
pods = []
|
||||
except Exception as exc:
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Failed to list pod from node {name} due to: {error}".format(
|
||||
name=self._module.params.get('name'), error=to_native(exc)))
|
||||
self._module.fail_json(
|
||||
msg="Failed to list pod from node {name} due to: {error}".format(
|
||||
name=self._module.params.get("name"), error=to_native(exc)
|
||||
)
|
||||
)
|
||||
|
||||
# Delete Pods
|
||||
if pods:
|
||||
self.evict_pods(pods)
|
||||
number_pod = len(pods)
|
||||
if self._drain_options.get('wait_timeout') is not None:
|
||||
warn = self.wait_for_pod_deletion(pods,
|
||||
self._drain_options.get('wait_timeout'),
|
||||
self._drain_options.get('wait_sleep'))
|
||||
if self._drain_options.get("wait_timeout") is not None:
|
||||
warn = self.wait_for_pod_deletion(
|
||||
pods,
|
||||
self._drain_options.get("wait_timeout"),
|
||||
self._drain_options.get("wait_sleep"),
|
||||
)
|
||||
if warn:
|
||||
warnings.append(warn)
|
||||
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
||||
if warnings:
|
||||
return dict(result=' '.join(result), warnings=warnings)
|
||||
return dict(result=' '.join(result))
|
||||
return dict(result=" ".join(result), warnings=warnings)
|
||||
return dict(result=" ".join(result))
|
||||
|
||||
def patch_node(self, unschedulable):
|
||||
|
||||
body = {
|
||||
'spec': {'unschedulable': unschedulable}
|
||||
}
|
||||
body = {"spec": {"unschedulable": unschedulable}}
|
||||
try:
|
||||
self._api_instance.patch_node(name=self._module.params.get('name'), body=body)
|
||||
self._api_instance.patch_node(
|
||||
name=self._module.params.get("name"), body=body
|
||||
)
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to patch node due to: {0}".format(to_native(exc)))
|
||||
self._module.fail_json(
|
||||
msg="Failed to patch node due to: {0}".format(to_native(exc))
|
||||
)
|
||||
|
||||
def execute_module(self):
|
||||
|
||||
state = self._module.params.get('state')
|
||||
name = self._module.params.get('name')
|
||||
state = self._module.params.get("state")
|
||||
name = self._module.params.get("name")
|
||||
try:
|
||||
node = self._api_instance.read_node(name=name)
|
||||
except ApiException as exc:
|
||||
if exc.reason == "Not Found":
|
||||
self._module.fail_json(msg="Node {0} not found.".format(name))
|
||||
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, exc.reason), status=exc.status)
|
||||
self._module.fail_json(
|
||||
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
||||
name, exc.reason
|
||||
),
|
||||
status=exc.status,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, to_native(exc)))
|
||||
self._module.fail_json(
|
||||
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
||||
name, to_native(exc)
|
||||
)
|
||||
)
|
||||
|
||||
result = {}
|
||||
if state == "cordon":
|
||||
if node.spec.unschedulable:
|
||||
self._module.exit_json(result="node {0} already marked unschedulable.".format(name))
|
||||
self._module.exit_json(
|
||||
result="node {0} already marked unschedulable.".format(name)
|
||||
)
|
||||
self.patch_node(unschedulable=True)
|
||||
result['result'] = "node {0} marked unschedulable.".format(name)
|
||||
result["result"] = "node {0} marked unschedulable.".format(name)
|
||||
self._changed = True
|
||||
|
||||
elif state == "uncordon":
|
||||
if not node.spec.unschedulable:
|
||||
self._module.exit_json(result="node {0} already marked schedulable.".format(name))
|
||||
self._module.exit_json(
|
||||
result="node {0} already marked schedulable.".format(name)
|
||||
)
|
||||
self.patch_node(unschedulable=False)
|
||||
result['result'] = "node {0} marked schedulable.".format(name)
|
||||
result["result"] = "node {0} marked schedulable.".format(name)
|
||||
self._changed = True
|
||||
|
||||
else:
|
||||
@@ -384,16 +460,16 @@ def argspec():
|
||||
state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
|
||||
name=dict(required=True),
|
||||
delete_options=dict(
|
||||
type='dict',
|
||||
type="dict",
|
||||
default={},
|
||||
options=dict(
|
||||
terminate_grace_period=dict(type='int'),
|
||||
force=dict(type='bool', default=False),
|
||||
ignore_daemonsets=dict(type='bool', default=False),
|
||||
disable_eviction=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int'),
|
||||
wait_sleep=dict(type='int', default=5),
|
||||
)
|
||||
terminate_grace_period=dict(type="int"),
|
||||
force=dict(type="bool", default=False),
|
||||
ignore_daemonsets=dict(type="bool", default=False),
|
||||
disable_eviction=dict(type="bool", default=False),
|
||||
wait_timeout=dict(type="int"),
|
||||
wait_sleep=dict(type="int", default=5),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -407,5 +483,5 @@ def main():
|
||||
k8s_drain.execute_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s_exec
|
||||
|
||||
@@ -63,9 +63,9 @@ options:
|
||||
- The command to execute
|
||||
type: str
|
||||
required: yes
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Execute a command
|
||||
kubernetes.core.k8s_exec:
|
||||
namespace: myproject
|
||||
@@ -84,9 +84,9 @@ EXAMPLES = r'''
|
||||
debug:
|
||||
msg: "cmd failed"
|
||||
when: command_status.rc != 0
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- The command object
|
||||
@@ -112,7 +112,7 @@ result:
|
||||
return_code:
|
||||
description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
|
||||
type: int
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
import shlex
|
||||
@@ -123,10 +123,12 @@ except ImportError:
|
||||
# ImportError are managed by the common module already.
|
||||
pass
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
AUTH_ARG_SPEC
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -139,10 +141,10 @@ except ImportError:
|
||||
|
||||
def argspec():
|
||||
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
spec['namespace'] = dict(type='str', required=True)
|
||||
spec['pod'] = dict(type='str', required=True)
|
||||
spec['container'] = dict(type='str')
|
||||
spec['command'] = dict(type='str', required=True)
|
||||
spec["namespace"] = dict(type="str", required=True)
|
||||
spec["pod"] = dict(type="str", required=True)
|
||||
spec["container"] = dict(type="str")
|
||||
spec["command"] = dict(type="str", required=True)
|
||||
return spec
|
||||
|
||||
|
||||
@@ -153,8 +155,8 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
|
||||
# hack because passing the container as None breaks things
|
||||
optional_kwargs = {}
|
||||
if module.params.get('container'):
|
||||
optional_kwargs['container'] = module.params['container']
|
||||
if module.params.get("container"):
|
||||
optional_kwargs["container"] = module.params["container"]
|
||||
try:
|
||||
resp = stream(
|
||||
api.connect_get_namespaced_pod_exec,
|
||||
@@ -165,10 +167,14 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
_preload_content=False, **optional_kwargs)
|
||||
_preload_content=False,
|
||||
**optional_kwargs
|
||||
)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to execute on pod %s"
|
||||
" due to : %s" % (module.params.get('pod'), to_native(e)))
|
||||
module.fail_json(
|
||||
msg="Failed to execute on pod %s"
|
||||
" due to : %s" % (module.params.get("pod"), to_native(e))
|
||||
)
|
||||
stdout, stderr, rc = [], [], 0
|
||||
while resp.is_open():
|
||||
resp.update(timeout=1)
|
||||
@@ -178,34 +184,37 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
stderr.append(resp.read_stderr())
|
||||
err = resp.read_channel(3)
|
||||
err = yaml.safe_load(err)
|
||||
if err['status'] == 'Success':
|
||||
if err["status"] == "Success":
|
||||
rc = 0
|
||||
else:
|
||||
rc = int(err['details']['causes'][0]['message'])
|
||||
rc = int(err["details"]["causes"][0]["message"])
|
||||
|
||||
module.deprecate("The 'return_code' return key is deprecated. Please use 'rc' instead.", version="4.0.0", collection_name="kubernetes.core")
|
||||
module.deprecate(
|
||||
"The 'return_code' return key is deprecated. Please use 'rc' instead.",
|
||||
version="4.0.0",
|
||||
collection_name="kubernetes.core",
|
||||
)
|
||||
module.exit_json(
|
||||
# Some command might change environment, but ultimately failing at end
|
||||
changed=True,
|
||||
stdout="".join(stdout),
|
||||
stderr="".join(stderr),
|
||||
rc=rc,
|
||||
return_code=rc
|
||||
return_code=rc,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=argspec(),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True,)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
module: k8s_info
|
||||
|
||||
short_description: Describe Kubernetes (K8s) objects
|
||||
@@ -52,9 +52,9 @@ requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Get an existing Service object
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: v1
|
||||
@@ -109,9 +109,9 @@ EXAMPLES = r'''
|
||||
namespace: default
|
||||
wait_sleep: 10
|
||||
wait_timeout: 360
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
api_found:
|
||||
description:
|
||||
- Whether the specified api_version and kind were successfully mapped to an existing API on the targeted cluster.
|
||||
@@ -144,12 +144,17 @@ resources:
|
||||
description: Current status details for the object.
|
||||
returned: success
|
||||
type: dict
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
WAIT_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
def execute_module(module, k8s_ansible_mixin):
|
||||
@@ -174,11 +179,11 @@ def argspec():
|
||||
args.update(
|
||||
dict(
|
||||
kind=dict(required=True),
|
||||
api_version=dict(default='v1', aliases=['api', 'version']),
|
||||
api_version=dict(default="v1", aliases=["api", "version"]),
|
||||
name=dict(),
|
||||
namespace=dict(),
|
||||
label_selectors=dict(type='list', elements='str', default=[]),
|
||||
field_selectors=dict(type='list', elements='str', default=[]),
|
||||
label_selectors=dict(type="list", elements="str", default=[]),
|
||||
field_selectors=dict(type="list", elements="str", default=[]),
|
||||
)
|
||||
)
|
||||
return args
|
||||
@@ -187,12 +192,14 @@ def argspec():
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
module: k8s_json_patch
|
||||
|
||||
short_description: Apply JSON patch operations to existing objects
|
||||
@@ -66,9 +66,9 @@ requirements:
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
- "jsonpatch"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Apply multiple patch operations to an existing Pod
|
||||
kubernetes.core.k8s_json_patch:
|
||||
kind: Pod
|
||||
@@ -81,9 +81,9 @@ EXAMPLES = r'''
|
||||
- op: replace
|
||||
patch: /spec/containers/0/image
|
||||
value: nginx
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description: The modified object.
|
||||
returned: success
|
||||
@@ -122,17 +122,24 @@ error:
|
||||
"msg": "Failed to import the required Python library (jsonpatch) ...",
|
||||
"exception": "Traceback (most recent call last): ..."
|
||||
}
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
WAIT_ARG_SPEC,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
get_api_client, K8sAnsibleMixin)
|
||||
get_api_client,
|
||||
K8sAnsibleMixin,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||
@@ -143,6 +150,7 @@ except ImportError:
|
||||
JSON_PATCH_IMPORT_ERR = None
|
||||
try:
|
||||
import jsonpatch
|
||||
|
||||
HAS_JSON_PATCH = True
|
||||
except ImportError:
|
||||
HAS_JSON_PATCH = False
|
||||
@@ -150,33 +158,18 @@ except ImportError:
|
||||
|
||||
|
||||
JSON_PATCH_ARGS = {
|
||||
'api_version': {
|
||||
'default': 'v1',
|
||||
'aliases': ['api', 'version'],
|
||||
},
|
||||
"kind": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
},
|
||||
"namespace": {
|
||||
"type": "str",
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
},
|
||||
"patch": {
|
||||
"type": "list",
|
||||
"required": True,
|
||||
"elements": "dict",
|
||||
},
|
||||
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
||||
"kind": {"type": "str", "required": True},
|
||||
"namespace": {"type": "str"},
|
||||
"name": {"type": "str", "required": True},
|
||||
"patch": {"type": "list", "required": True, "elements": "dict"},
|
||||
}
|
||||
|
||||
|
||||
def json_patch(existing, patch):
|
||||
if not HAS_JSON_PATCH:
|
||||
error = {
|
||||
"msg": missing_required_lib('jsonpatch'),
|
||||
"msg": missing_required_lib("jsonpatch"),
|
||||
"exception": JSON_PATCH_IMPORT_ERR,
|
||||
}
|
||||
return None, error
|
||||
@@ -185,16 +178,10 @@ def json_patch(existing, patch):
|
||||
patched = patch.apply(existing)
|
||||
return patched, None
|
||||
except jsonpatch.InvalidJsonPatch as e:
|
||||
error = {
|
||||
"msg": "Invalid JSON patch",
|
||||
"exception": e
|
||||
}
|
||||
error = {"msg": "Invalid JSON patch", "exception": e}
|
||||
return None, error
|
||||
except jsonpatch.JsonPatchConflict as e:
|
||||
error = {
|
||||
"msg": "Patch could not be applied due to a conflict",
|
||||
"exception": e
|
||||
}
|
||||
error = {"msg": "Patch could not be applied due to a conflict", "exception": e}
|
||||
return None, error
|
||||
|
||||
|
||||
@@ -209,15 +196,14 @@ def execute_module(k8s_module, module):
|
||||
wait_sleep = module.params.get("wait_sleep")
|
||||
wait_timeout = module.params.get("wait_timeout")
|
||||
wait_condition = None
|
||||
if module.params.get("wait_condition") and module.params.get("wait_condition").get("type"):
|
||||
wait_condition = module.params['wait_condition']
|
||||
if module.params.get("wait_condition") and module.params.get("wait_condition").get(
|
||||
"type"
|
||||
):
|
||||
wait_condition = module.params["wait_condition"]
|
||||
# definition is needed for wait
|
||||
definition = {
|
||||
"kind": kind,
|
||||
"metadata": {
|
||||
"name": name,
|
||||
"namespace": namespace,
|
||||
}
|
||||
"metadata": {"name": name, "namespace": namespace},
|
||||
}
|
||||
|
||||
def build_error_msg(kind, name, msg):
|
||||
@@ -228,11 +214,18 @@ def execute_module(k8s_module, module):
|
||||
try:
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
except DynamicApiError as exc:
|
||||
msg = 'Failed to retrieve requested object: {0}'.format(exc.body)
|
||||
module.fail_json(msg=build_error_msg(kind, name, msg), error=exc.status, status=exc.status, reason=exc.reason)
|
||||
msg = "Failed to retrieve requested object: {0}".format(exc.body)
|
||||
module.fail_json(
|
||||
msg=build_error_msg(kind, name, msg),
|
||||
error=exc.status,
|
||||
status=exc.status,
|
||||
reason=exc.reason,
|
||||
)
|
||||
except ValueError as exc:
|
||||
msg = 'Failed to retrieve requested object: {0}'.format(to_native(exc))
|
||||
module.fail_json(msg=build_error_msg(kind, name, msg), error='', status='', reason='')
|
||||
msg = "Failed to retrieve requested object: {0}".format(to_native(exc))
|
||||
module.fail_json(
|
||||
msg=build_error_msg(kind, name, msg), error="", status="", reason=""
|
||||
)
|
||||
|
||||
if module.check_mode and not k8s_module.supports_dry_run:
|
||||
obj, error = json_patch(existing.to_dict(), patch)
|
||||
@@ -243,18 +236,28 @@ def execute_module(k8s_module, module):
|
||||
if module.check_mode:
|
||||
params["dry_run"] = "All"
|
||||
try:
|
||||
obj = resource.patch(patch, name=name, namespace=namespace, content_type="application/json-patch+json", **params).to_dict()
|
||||
obj = resource.patch(
|
||||
patch,
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
content_type="application/json-patch+json",
|
||||
**params
|
||||
).to_dict()
|
||||
except DynamicApiError as exc:
|
||||
msg = "Failed to patch existing object: {0}".format(exc.body)
|
||||
module.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
|
||||
module.fail_json(
|
||||
msg=msg, error=exc.status, status=exc.status, reason=exc.reason
|
||||
)
|
||||
except Exception as exc:
|
||||
msg = "Failed to patch existing object: {0}".format(exc)
|
||||
module.fail_json(msg=msg, error=to_native(exc), status='', reason='')
|
||||
module.fail_json(msg=msg, error=to_native(exc), status="", reason="")
|
||||
|
||||
success = True
|
||||
result = {"result": obj}
|
||||
if wait and not module.check_mode:
|
||||
success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
success, result["result"], result["duration"] = k8s_module.wait(
|
||||
resource, definition, wait_sleep, wait_timeout, condition=wait_condition
|
||||
)
|
||||
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
||||
result["changed"] = not match
|
||||
if module._diff:
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
module: k8s_log
|
||||
|
||||
short_description: Fetch logs from Kubernetes resources
|
||||
@@ -65,9 +65,9 @@ requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Get a log from a Pod
|
||||
kubernetes.core.k8s_log:
|
||||
name: example-1
|
||||
@@ -100,9 +100,9 @@ EXAMPLES = r'''
|
||||
namespace: testing
|
||||
name: example
|
||||
register: log
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
log:
|
||||
type: str
|
||||
description:
|
||||
@@ -113,15 +113,20 @@ log_lines:
|
||||
description:
|
||||
- The log of the object, split on newlines
|
||||
returned: success
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible.module_utils.six import PY2
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
NAME_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
def argspec():
|
||||
@@ -129,55 +134,62 @@ def argspec():
|
||||
args.update(NAME_ARG_SPEC)
|
||||
args.update(
|
||||
dict(
|
||||
kind=dict(type='str', default='Pod'),
|
||||
kind=dict(type="str", default="Pod"),
|
||||
container=dict(),
|
||||
since_seconds=dict(),
|
||||
label_selectors=dict(type='list', elements='str', default=[]),
|
||||
label_selectors=dict(type="list", elements="str", default=[]),
|
||||
)
|
||||
)
|
||||
return args
|
||||
|
||||
|
||||
def execute_module(module, k8s_ansible_mixin):
|
||||
name = module.params.get('name')
|
||||
namespace = module.params.get('namespace')
|
||||
label_selector = ','.join(module.params.get('label_selectors', {}))
|
||||
name = module.params.get("name")
|
||||
namespace = module.params.get("namespace")
|
||||
label_selector = ",".join(module.params.get("label_selectors", {}))
|
||||
if name and label_selector:
|
||||
module.fail(msg='Only one of name or label_selectors can be provided')
|
||||
module.fail(msg="Only one of name or label_selectors can be provided")
|
||||
|
||||
resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
|
||||
v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
|
||||
resource = k8s_ansible_mixin.find_resource(
|
||||
module.params["kind"], module.params["api_version"], fail=True
|
||||
)
|
||||
v1_pods = k8s_ansible_mixin.find_resource("Pod", "v1", fail=True)
|
||||
|
||||
if 'log' not in resource.subresources:
|
||||
if "log" not in resource.subresources:
|
||||
if not name:
|
||||
module.fail(msg='name must be provided for resources that do not support the log subresource')
|
||||
module.fail(
|
||||
msg="name must be provided for resources that do not support the log subresource"
|
||||
)
|
||||
instance = resource.get(name=name, namespace=namespace)
|
||||
label_selector = ','.join(extract_selectors(module, instance))
|
||||
label_selector = ",".join(extract_selectors(module, instance))
|
||||
resource = v1_pods
|
||||
|
||||
if label_selector:
|
||||
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
|
||||
if not instances.items:
|
||||
module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
|
||||
module.fail(
|
||||
msg="No pods in namespace {0} matched selector {1}".format(
|
||||
namespace, label_selector
|
||||
)
|
||||
)
|
||||
# This matches the behavior of kubectl when logging pods via a selector
|
||||
name = instances.items[0].metadata.name
|
||||
resource = v1_pods
|
||||
|
||||
kwargs = {}
|
||||
if module.params.get('container'):
|
||||
kwargs['query_params'] = dict(container=module.params['container'])
|
||||
if module.params.get("container"):
|
||||
kwargs["query_params"] = dict(container=module.params["container"])
|
||||
|
||||
if module.params.get('since_seconds'):
|
||||
kwargs.setdefault('query_params', {}).update({'sinceSeconds': module.params['since_seconds']})
|
||||
if module.params.get("since_seconds"):
|
||||
kwargs.setdefault("query_params", {}).update(
|
||||
{"sinceSeconds": module.params["since_seconds"]}
|
||||
)
|
||||
|
||||
log = serialize_log(resource.log.get(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
serialize=False,
|
||||
**kwargs
|
||||
))
|
||||
log = serialize_log(
|
||||
resource.log.get(name=name, namespace=namespace, serialize=False, **kwargs)
|
||||
)
|
||||
|
||||
module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
|
||||
module.exit_json(changed=False, log=log, log_lines=log.split("\n"))
|
||||
|
||||
|
||||
def extract_selectors(module, instance):
|
||||
@@ -185,35 +197,46 @@ def extract_selectors(module, instance):
|
||||
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||
selectors = []
|
||||
if not instance.spec.selector:
|
||||
module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
|
||||
'/'.join(instance.group, instance.apiVersion), instance.kind))
|
||||
module.fail(
|
||||
msg="{0} {1} does not support the log subresource directly, and no Pod selector was found on the object".format(
|
||||
"/".join(instance.group, instance.apiVersion), instance.kind
|
||||
)
|
||||
)
|
||||
|
||||
if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
|
||||
if not (
|
||||
instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions
|
||||
):
|
||||
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
|
||||
for k, v in dict(instance.spec.selector).items():
|
||||
selectors.append('{0}={1}'.format(k, v))
|
||||
selectors.append("{0}={1}".format(k, v))
|
||||
return selectors
|
||||
|
||||
if instance.spec.selector.matchLabels:
|
||||
for k, v in dict(instance.spec.selector.matchLabels).items():
|
||||
selectors.append('{0}={1}'.format(k, v))
|
||||
selectors.append("{0}={1}".format(k, v))
|
||||
|
||||
if instance.spec.selector.matchExpressions:
|
||||
for expression in instance.spec.selector.matchExpressions:
|
||||
operator = expression.operator
|
||||
|
||||
if operator == 'Exists':
|
||||
if operator == "Exists":
|
||||
selectors.append(expression.key)
|
||||
elif operator == 'DoesNotExist':
|
||||
selectors.append('!{0}'.format(expression.key))
|
||||
elif operator in ['In', 'NotIn']:
|
||||
selectors.append('{key} {operator} {values}'.format(
|
||||
key=expression.key,
|
||||
operator=operator.lower(),
|
||||
values='({0})'.format(', '.join(expression.values))
|
||||
))
|
||||
elif operator == "DoesNotExist":
|
||||
selectors.append("!{0}".format(expression.key))
|
||||
elif operator in ["In", "NotIn"]:
|
||||
selectors.append(
|
||||
"{key} {operator} {values}".format(
|
||||
key=expression.key,
|
||||
operator=operator.lower(),
|
||||
values="({0})".format(", ".join(expression.values)),
|
||||
)
|
||||
)
|
||||
else:
|
||||
module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
|
||||
module.fail(
|
||||
msg="The k8s_log module does not support the {0} matchExpression operator".format(
|
||||
operator.lower()
|
||||
)
|
||||
)
|
||||
|
||||
return selectors
|
||||
|
||||
@@ -221,18 +244,20 @@ def extract_selectors(module, instance):
|
||||
def serialize_log(response):
|
||||
if PY2:
|
||||
return response.data
|
||||
return response.data.decode('utf8')
|
||||
return response.data.decode("utf8")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
# 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'''
|
||||
DOCUMENTATION = r"""
|
||||
module: k8s_rollback
|
||||
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
|
||||
version_added: "1.0.0"
|
||||
@@ -34,18 +35,18 @@ requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Rollback a failed deployment
|
||||
kubernetes.core.k8s_rollback:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
name: web
|
||||
namespace: testing
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
rollback_info:
|
||||
description:
|
||||
- The object that was rolled back.
|
||||
@@ -74,25 +75,29 @@ rollback_info:
|
||||
description: Current status details for the object.
|
||||
returned: success
|
||||
type: dict
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||
AUTH_ARG_SPEC,
|
||||
NAME_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
def get_managed_resource(module):
|
||||
managed_resource = {}
|
||||
|
||||
kind = module.params['kind']
|
||||
kind = module.params["kind"]
|
||||
if kind == "DaemonSet":
|
||||
managed_resource['kind'] = "ControllerRevision"
|
||||
managed_resource['api_version'] = "apps/v1"
|
||||
managed_resource["kind"] = "ControllerRevision"
|
||||
managed_resource["api_version"] = "apps/v1"
|
||||
elif kind == "Deployment":
|
||||
managed_resource['kind'] = "ReplicaSet"
|
||||
managed_resource['api_version'] = "apps/v1"
|
||||
managed_resource["kind"] = "ReplicaSet"
|
||||
managed_resource["api_version"] = "apps/v1"
|
||||
else:
|
||||
module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
|
||||
return managed_resource
|
||||
@@ -102,80 +107,89 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
results = []
|
||||
|
||||
resources = k8s_ansible_mixin.kubernetes_facts(
|
||||
module.params['kind'],
|
||||
module.params['api_version'],
|
||||
module.params['name'],
|
||||
module.params['namespace'],
|
||||
module.params['label_selectors'],
|
||||
module.params['field_selectors'])
|
||||
module.params["kind"],
|
||||
module.params["api_version"],
|
||||
module.params["name"],
|
||||
module.params["namespace"],
|
||||
module.params["label_selectors"],
|
||||
module.params["field_selectors"],
|
||||
)
|
||||
|
||||
for resource in resources['resources']:
|
||||
for resource in resources["resources"]:
|
||||
result = perform_action(module, k8s_ansible_mixin, resource)
|
||||
results.append(result)
|
||||
|
||||
module.exit_json(**{
|
||||
'changed': True,
|
||||
'rollback_info': results
|
||||
})
|
||||
module.exit_json(**{"changed": True, "rollback_info": results})
|
||||
|
||||
|
||||
def perform_action(module, k8s_ansible_mixin, resource):
|
||||
if module.params['kind'] == "DaemonSet":
|
||||
current_revision = resource['metadata']['generation']
|
||||
elif module.params['kind'] == "Deployment":
|
||||
current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
||||
if module.params["kind"] == "DaemonSet":
|
||||
current_revision = resource["metadata"]["generation"]
|
||||
elif module.params["kind"] == "Deployment":
|
||||
current_revision = resource["metadata"]["annotations"][
|
||||
"deployment.kubernetes.io/revision"
|
||||
]
|
||||
|
||||
managed_resource = get_managed_resource(module)
|
||||
managed_resources = k8s_ansible_mixin.kubernetes_facts(
|
||||
managed_resource['kind'],
|
||||
managed_resource['api_version'],
|
||||
'',
|
||||
module.params['namespace'],
|
||||
resource['spec']
|
||||
['selector']
|
||||
['matchLabels'],
|
||||
'')
|
||||
managed_resource["kind"],
|
||||
managed_resource["api_version"],
|
||||
"",
|
||||
module.params["namespace"],
|
||||
resource["spec"]["selector"]["matchLabels"],
|
||||
"",
|
||||
)
|
||||
|
||||
prev_managed_resource = get_previous_revision(managed_resources['resources'],
|
||||
current_revision)
|
||||
prev_managed_resource = get_previous_revision(
|
||||
managed_resources["resources"], current_revision
|
||||
)
|
||||
|
||||
if module.params['kind'] == "Deployment":
|
||||
del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
|
||||
if module.params["kind"] == "Deployment":
|
||||
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
||||
"pod-template-hash"
|
||||
]
|
||||
|
||||
resource_patch = [{
|
||||
"op": "replace",
|
||||
"path": "/spec/template",
|
||||
"value": prev_managed_resource['spec']['template']
|
||||
}, {
|
||||
"op": "replace",
|
||||
"path": "/metadata/annotations",
|
||||
"value": {
|
||||
"deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
||||
}
|
||||
}]
|
||||
resource_patch = [
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/spec/template",
|
||||
"value": prev_managed_resource["spec"]["template"],
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/metadata/annotations",
|
||||
"value": {
|
||||
"deployment.kubernetes.io/revision": prev_managed_resource[
|
||||
"metadata"
|
||||
]["annotations"]["deployment.kubernetes.io/revision"]
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
api_target = 'deployments'
|
||||
content_type = 'application/json-patch+json'
|
||||
elif module.params['kind'] == "DaemonSet":
|
||||
api_target = "deployments"
|
||||
content_type = "application/json-patch+json"
|
||||
elif module.params["kind"] == "DaemonSet":
|
||||
resource_patch = prev_managed_resource["data"]
|
||||
|
||||
api_target = 'daemonsets'
|
||||
content_type = 'application/strategic-merge-patch+json'
|
||||
api_target = "daemonsets"
|
||||
content_type = "application/strategic-merge-patch+json"
|
||||
|
||||
rollback = k8s_ansible_mixin.client.request(
|
||||
"PATCH",
|
||||
"/apis/{0}/namespaces/{1}/{2}/{3}"
|
||||
.format(module.params['api_version'],
|
||||
module.params['namespace'],
|
||||
api_target,
|
||||
module.params['name']),
|
||||
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
||||
module.params["api_version"],
|
||||
module.params["namespace"],
|
||||
api_target,
|
||||
module.params["name"],
|
||||
),
|
||||
body=resource_patch,
|
||||
content_type=content_type)
|
||||
content_type=content_type,
|
||||
)
|
||||
|
||||
result = {'changed': True}
|
||||
result['method'] = 'patch'
|
||||
result['body'] = resource_patch
|
||||
result['resources'] = rollback.to_dict()
|
||||
result = {"changed": True}
|
||||
result["method"] = "patch"
|
||||
result["body"] = resource_patch
|
||||
result["resources"] = rollback.to_dict()
|
||||
return result
|
||||
|
||||
|
||||
@@ -184,8 +198,8 @@ def argspec():
|
||||
args.update(NAME_ARG_SPEC)
|
||||
args.update(
|
||||
dict(
|
||||
label_selectors=dict(type='list', elements='str', default=[]),
|
||||
field_selectors=dict(type='list', elements='str', default=[]),
|
||||
label_selectors=dict(type="list", elements="str", default=[]),
|
||||
field_selectors=dict(type="list", elements="str", default=[]),
|
||||
)
|
||||
)
|
||||
return args
|
||||
@@ -193,27 +207,40 @@ def argspec():
|
||||
|
||||
def get_previous_revision(all_resources, current_revision):
|
||||
for resource in all_resources:
|
||||
if resource['kind'] == 'ReplicaSet':
|
||||
if int(resource['metadata']
|
||||
['annotations']
|
||||
['deployment.kubernetes.io/revision']) == int(current_revision) - 1:
|
||||
if resource["kind"] == "ReplicaSet":
|
||||
if (
|
||||
int(
|
||||
resource["metadata"]["annotations"][
|
||||
"deployment.kubernetes.io/revision"
|
||||
]
|
||||
)
|
||||
== int(current_revision) - 1
|
||||
):
|
||||
return resource
|
||||
elif resource['kind'] == 'ControllerRevision':
|
||||
if int(resource['metadata']
|
||||
['annotations']
|
||||
['deprecated.daemonset.template.generation']) == int(current_revision) - 1:
|
||||
elif resource["kind"] == "ControllerRevision":
|
||||
if (
|
||||
int(
|
||||
resource["metadata"]["annotations"][
|
||||
"deprecated.daemonset.template.generation"
|
||||
]
|
||||
)
|
||||
== int(current_revision) - 1
|
||||
):
|
||||
return resource
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (K8sAnsibleMixin, get_api_client)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s_scale
|
||||
|
||||
@@ -48,9 +48,9 @@ requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Scale deployment up, and extend timeout
|
||||
kubernetes.core.k8s_scale:
|
||||
api_version: v1
|
||||
@@ -105,9 +105,9 @@ EXAMPLES = r'''
|
||||
label_selectors:
|
||||
- app=test
|
||||
continue_on_error: true
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- If a change was made, will return the patched object, otherwise returns the existing object.
|
||||
@@ -139,133 +139,166 @@ result:
|
||||
returned: when C(wait) is true
|
||||
type: int
|
||||
sample: 48
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
|
||||
AUTH_ARG_SPEC,
|
||||
RESOURCE_ARG_SPEC,
|
||||
NAME_ARG_SPEC,
|
||||
)
|
||||
|
||||
|
||||
SCALE_ARG_SPEC = {
|
||||
'replicas': {'type': 'int', 'required': True},
|
||||
'current_replicas': {'type': 'int'},
|
||||
'resource_version': {},
|
||||
'wait': {'type': 'bool', 'default': True},
|
||||
'wait_timeout': {'type': 'int', 'default': 20},
|
||||
'wait_sleep': {'type': 'int', 'default': 5},
|
||||
"replicas": {"type": "int", "required": True},
|
||||
"current_replicas": {"type": "int"},
|
||||
"resource_version": {},
|
||||
"wait": {"type": "bool", "default": True},
|
||||
"wait_timeout": {"type": "int", "default": 20},
|
||||
"wait_sleep": {"type": "int", "default": 5},
|
||||
}
|
||||
|
||||
|
||||
def execute_module(module, k8s_ansible_mixin,):
|
||||
def execute_module(
|
||||
module, k8s_ansible_mixin,
|
||||
):
|
||||
k8s_ansible_mixin.set_resource_definitions(module)
|
||||
|
||||
definition = k8s_ansible_mixin.resource_definitions[0]
|
||||
|
||||
name = definition['metadata']['name']
|
||||
namespace = definition['metadata'].get('namespace')
|
||||
api_version = definition['apiVersion']
|
||||
kind = definition['kind']
|
||||
current_replicas = module.params.get('current_replicas')
|
||||
replicas = module.params.get('replicas')
|
||||
resource_version = module.params.get('resource_version')
|
||||
name = definition["metadata"]["name"]
|
||||
namespace = definition["metadata"].get("namespace")
|
||||
api_version = definition["apiVersion"]
|
||||
kind = definition["kind"]
|
||||
current_replicas = module.params.get("current_replicas")
|
||||
replicas = module.params.get("replicas")
|
||||
resource_version = module.params.get("resource_version")
|
||||
|
||||
label_selectors = module.params.get('label_selectors')
|
||||
label_selectors = module.params.get("label_selectors")
|
||||
if not label_selectors:
|
||||
label_selectors = []
|
||||
continue_on_error = module.params.get('continue_on_error')
|
||||
continue_on_error = module.params.get("continue_on_error")
|
||||
|
||||
wait = module.params.get('wait')
|
||||
wait_time = module.params.get('wait_timeout')
|
||||
wait_sleep = module.params.get('wait_sleep')
|
||||
wait = module.params.get("wait")
|
||||
wait_time = module.params.get("wait_timeout")
|
||||
wait_sleep = module.params.get("wait_sleep")
|
||||
existing = None
|
||||
existing_count = None
|
||||
return_attributes = dict(result=dict())
|
||||
if module._diff:
|
||||
return_attributes['diff'] = dict()
|
||||
return_attributes["diff"] = dict()
|
||||
if wait:
|
||||
return_attributes['duration'] = 0
|
||||
return_attributes["duration"] = 0
|
||||
|
||||
resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True)
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import NotFoundError
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
NotFoundError,
|
||||
)
|
||||
|
||||
multiple_scale = False
|
||||
try:
|
||||
existing = resource.get(name=name, namespace=namespace, label_selector=','.join(label_selectors))
|
||||
if existing.kind.endswith('List'):
|
||||
existing = resource.get(
|
||||
name=name, namespace=namespace, label_selector=",".join(label_selectors)
|
||||
)
|
||||
if existing.kind.endswith("List"):
|
||||
existing_items = existing.items
|
||||
multiple_scale = len(existing_items) > 1
|
||||
else:
|
||||
existing_items = [existing]
|
||||
except NotFoundError as exc:
|
||||
module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
||||
error=exc.value.get('status'))
|
||||
module.fail_json(
|
||||
msg="Failed to retrieve requested object: {0}".format(exc),
|
||||
error=exc.value.get("status"),
|
||||
)
|
||||
|
||||
if multiple_scale:
|
||||
# when scaling multiple resource, the 'result' is changed to 'results' and is a list
|
||||
return_attributes = {'results': []}
|
||||
return_attributes = {"results": []}
|
||||
changed = False
|
||||
|
||||
def _continue_or_fail(error):
|
||||
if multiple_scale and continue_on_error:
|
||||
if "errors" not in return_attributes:
|
||||
return_attributes['errors'] = []
|
||||
return_attributes['errors'].append({'error': error, 'failed': True})
|
||||
return_attributes["errors"] = []
|
||||
return_attributes["errors"].append({"error": error, "failed": True})
|
||||
else:
|
||||
module.fail_json(msg=error, **return_attributes)
|
||||
|
||||
def _continue_or_exit(warn):
|
||||
if multiple_scale:
|
||||
return_attributes['results'].append({'warning': warn, 'changed': False})
|
||||
return_attributes["results"].append({"warning": warn, "changed": False})
|
||||
else:
|
||||
module.exit_json(warning=warn, **return_attributes)
|
||||
|
||||
for existing in existing_items:
|
||||
if module.params['kind'] == 'job':
|
||||
if module.params["kind"] == "job":
|
||||
existing_count = existing.spec.parallelism
|
||||
elif hasattr(existing.spec, 'replicas'):
|
||||
elif hasattr(existing.spec, "replicas"):
|
||||
existing_count = existing.spec.replicas
|
||||
|
||||
if existing_count is None:
|
||||
error = 'Failed to retrieve the available count for object kind={0} name={1} namespace={2}.'.format(
|
||||
existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||
error = "Failed to retrieve the available count for object kind={0} name={1} namespace={2}.".format(
|
||||
existing.kind, existing.metadata.name, existing.metadata.namespace
|
||||
)
|
||||
_continue_or_fail(error)
|
||||
continue
|
||||
|
||||
if resource_version and resource_version != existing.metadata.resourceVersion:
|
||||
warn = 'expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
|
||||
resource_version, existing.metadata.resourceVersion, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||
warn = "expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
|
||||
resource_version,
|
||||
existing.metadata.resourceVersion,
|
||||
existing.kind,
|
||||
existing.metadata.name,
|
||||
existing.metadata.namespace,
|
||||
)
|
||||
_continue_or_exit(warn)
|
||||
continue
|
||||
|
||||
if current_replicas is not None and existing_count != current_replicas:
|
||||
warn = 'current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
|
||||
current_replicas, existing_count, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||
warn = "current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
|
||||
current_replicas,
|
||||
existing_count,
|
||||
existing.kind,
|
||||
existing.metadata.name,
|
||||
existing.metadata.namespace,
|
||||
)
|
||||
_continue_or_exit(warn)
|
||||
continue
|
||||
|
||||
if existing_count != replicas:
|
||||
if not module.check_mode:
|
||||
if module.params['kind'] == 'job':
|
||||
if module.params["kind"] == "job":
|
||||
existing.spec.parallelism = replicas
|
||||
result = resource.patch(existing.to_dict()).to_dict()
|
||||
else:
|
||||
result = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time, wait_sleep)
|
||||
changed = changed or result['changed']
|
||||
result = scale(
|
||||
module,
|
||||
k8s_ansible_mixin,
|
||||
resource,
|
||||
existing,
|
||||
replicas,
|
||||
wait,
|
||||
wait_time,
|
||||
wait_sleep,
|
||||
)
|
||||
changed = changed or result["changed"]
|
||||
else:
|
||||
name = existing.metadata.name
|
||||
namespace = existing.metadata.namespace
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
result = {'changed': False, 'result': existing.to_dict()}
|
||||
result = {"changed": False, "result": existing.to_dict()}
|
||||
if module._diff:
|
||||
result['diff'] = {}
|
||||
result["diff"] = {}
|
||||
if wait:
|
||||
result['duration'] = 0
|
||||
result["duration"] = 0
|
||||
# append result to the return attribute
|
||||
if multiple_scale:
|
||||
return_attributes['results'].append(result)
|
||||
return_attributes["results"].append(result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
@@ -277,22 +310,35 @@ def argspec():
|
||||
args.update(RESOURCE_ARG_SPEC)
|
||||
args.update(NAME_ARG_SPEC)
|
||||
args.update(AUTH_ARG_SPEC)
|
||||
args.update({'label_selectors': {'type': 'list', 'elements': 'str', 'default': []}})
|
||||
args.update(({'continue_on_error': {'type': 'bool', 'default': False}}))
|
||||
args.update({"label_selectors": {"type": "list", "elements": "str", "default": []}})
|
||||
args.update(({"continue_on_error": {"type": "bool", "default": False}}))
|
||||
return args
|
||||
|
||||
|
||||
def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time, wait_sleep):
|
||||
def scale(
|
||||
module,
|
||||
k8s_ansible_mixin,
|
||||
resource,
|
||||
existing_object,
|
||||
replicas,
|
||||
wait,
|
||||
wait_time,
|
||||
wait_sleep,
|
||||
):
|
||||
name = existing_object.metadata.name
|
||||
namespace = existing_object.metadata.namespace
|
||||
kind = existing_object.kind
|
||||
|
||||
if not hasattr(resource, 'scale'):
|
||||
if not hasattr(resource, "scale"):
|
||||
module.fail_json(
|
||||
msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
|
||||
)
|
||||
|
||||
scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
|
||||
scale_obj = {
|
||||
"kind": kind,
|
||||
"metadata": {"name": name, "namespace": namespace},
|
||||
"spec": {"replicas": replicas},
|
||||
}
|
||||
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
|
||||
@@ -304,13 +350,15 @@ def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait,
|
||||
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
|
||||
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
||||
result = dict()
|
||||
result['result'] = k8s_obj
|
||||
result['changed'] = not match
|
||||
result["result"] = k8s_obj
|
||||
result["changed"] = not match
|
||||
if module._diff:
|
||||
result['diff'] = diffs
|
||||
result["diff"] = diffs
|
||||
|
||||
if wait:
|
||||
success, result['result'], result['duration'] = k8s_ansible_mixin.wait(resource, scale_obj, wait_sleep, wait_time)
|
||||
success, result["result"], result["duration"] = k8s_ansible_mixin.wait(
|
||||
resource, scale_obj, wait_sleep, wait_time
|
||||
)
|
||||
if not success:
|
||||
module.fail_json(msg="Resource scaling timed out", **result)
|
||||
return result
|
||||
@@ -318,15 +366,22 @@ def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait,
|
||||
|
||||
def main():
|
||||
mutually_exclusive = [
|
||||
('resource_definition', 'src'),
|
||||
("resource_definition", "src"),
|
||||
]
|
||||
module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argspec(),
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
|
||||
module: k8s_service
|
||||
|
||||
@@ -85,9 +85,9 @@ options:
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
- name: Expose https port with ClusterIP
|
||||
kubernetes.core.k8s_service:
|
||||
state: present
|
||||
@@ -111,9 +111,9 @@ EXAMPLES = r'''
|
||||
protocol: TCP
|
||||
selector:
|
||||
key: special
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = r'''
|
||||
RETURN = r"""
|
||||
result:
|
||||
description:
|
||||
- The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.
|
||||
@@ -140,32 +140,36 @@ result:
|
||||
description: Current status details for the object.
|
||||
returned: success
|
||||
type: complex
|
||||
'''
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
|
||||
AUTH_ARG_SPEC,
|
||||
COMMON_ARG_SPEC,
|
||||
RESOURCE_ARG_SPEC,
|
||||
)
|
||||
|
||||
SERVICE_ARG_SPEC = {
|
||||
'apply': {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
"apply": {"type": "bool", "default": False},
|
||||
"name": {"required": True},
|
||||
"namespace": {"required": True},
|
||||
"merge_type": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
"choices": ["json", "merge", "strategic-merge"],
|
||||
},
|
||||
'name': {'required': True},
|
||||
'namespace': {'required': True},
|
||||
'merge_type': {'type': 'list', 'elements': 'str', 'choices': ['json', 'merge', 'strategic-merge']},
|
||||
'selector': {'type': 'dict'},
|
||||
'type': {
|
||||
'type': 'str',
|
||||
'choices': [
|
||||
'NodePort', 'ClusterIP', 'LoadBalancer', 'ExternalName'
|
||||
],
|
||||
"selector": {"type": "dict"},
|
||||
"type": {
|
||||
"type": "str",
|
||||
"choices": ["NodePort", "ClusterIP", "LoadBalancer", "ExternalName"],
|
||||
},
|
||||
'ports': {'type': 'list', 'elements': 'dict'},
|
||||
"ports": {"type": "list", "elements": "dict"},
|
||||
}
|
||||
|
||||
|
||||
@@ -195,29 +199,31 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
""" Module execution """
|
||||
k8s_ansible_mixin.set_resource_definitions(module)
|
||||
|
||||
api_version = 'v1'
|
||||
selector = module.params.get('selector')
|
||||
service_type = module.params.get('type')
|
||||
ports = module.params.get('ports')
|
||||
api_version = "v1"
|
||||
selector = module.params.get("selector")
|
||||
service_type = module.params.get("type")
|
||||
ports = module.params.get("ports")
|
||||
|
||||
definition = defaultdict(defaultdict)
|
||||
|
||||
definition['kind'] = 'Service'
|
||||
definition['apiVersion'] = api_version
|
||||
definition["kind"] = "Service"
|
||||
definition["apiVersion"] = api_version
|
||||
|
||||
def_spec = definition['spec']
|
||||
def_spec['type'] = service_type
|
||||
def_spec['ports'] = ports
|
||||
def_spec['selector'] = selector
|
||||
def_spec = definition["spec"]
|
||||
def_spec["type"] = service_type
|
||||
def_spec["ports"] = ports
|
||||
def_spec["selector"] = selector
|
||||
|
||||
def_meta = definition['metadata']
|
||||
def_meta['name'] = module.params.get('name')
|
||||
def_meta['namespace'] = module.params.get('namespace')
|
||||
def_meta = definition["metadata"]
|
||||
def_meta["name"] = module.params.get("name")
|
||||
def_meta["namespace"] = module.params.get("namespace")
|
||||
|
||||
# 'resource_definition:' has lower priority than module parameters
|
||||
definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
|
||||
definition = dict(
|
||||
merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition)
|
||||
)
|
||||
|
||||
resource = k8s_ansible_mixin.find_resource('Service', api_version, fail=True)
|
||||
resource = k8s_ansible_mixin.find_resource("Service", api_version, fail=True)
|
||||
definition = k8s_ansible_mixin.set_defaults(resource, definition)
|
||||
result = k8s_ansible_mixin.perform_action(resource, definition)
|
||||
|
||||
@@ -227,12 +233,14 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user