mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-12 12:32:05 +00:00
Compare commits
20 Commits
2.4.0
...
stable-2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0de560df27 | ||
|
|
243bc00599 | ||
|
|
7e00e1ef87 | ||
|
|
1b412e06ab | ||
|
|
11c800d6ed | ||
|
|
0d9c4d3459 | ||
|
|
3645c1c16c | ||
|
|
5fdd70cbc3 | ||
|
|
1c02fe3443 | ||
|
|
eaffde63bb | ||
|
|
d8538ffed3 | ||
|
|
bc168a5727 | ||
|
|
dc5a1e6dd1 | ||
|
|
72536fe286 | ||
|
|
f2d899b939 | ||
|
|
bc391218a4 | ||
|
|
83b3a1aa39 | ||
|
|
aa41055503 | ||
|
|
256fa58ca8 | ||
|
|
70db517265 |
4
.github/patchback.yml
vendored
4
.github/patchback.yml
vendored
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
backport_branch_prefix: patchback/backports/
|
|
||||||
backport_label_prefix: backport-
|
|
||||||
target_branch_prefix: stable-
|
|
||||||
10
.zuul.d/network-ee-sanity-tests_non-voting.yaml
Normal file
10
.zuul.d/network-ee-sanity-tests_non-voting.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- project:
|
||||||
|
name: github.com/ansible-collections/kubernetes.core
|
||||||
|
check:
|
||||||
|
jobs:
|
||||||
|
- network-ee-sanity-tests:
|
||||||
|
voting: false
|
||||||
|
gate:
|
||||||
|
jobs:
|
||||||
|
- network-ee-sanity-tests:
|
||||||
|
voting: false
|
||||||
134
CHANGELOG.rst
134
CHANGELOG.rst
@@ -5,150 +5,36 @@ Kubernetes Collection Release Notes
|
|||||||
.. contents:: Topics
|
.. contents:: Topics
|
||||||
|
|
||||||
|
|
||||||
v2.4.0
|
v2.2.3
|
||||||
======
|
======
|
||||||
|
|
||||||
Major Changes
|
Bugfixes
|
||||||
-------------
|
--------
|
||||||
|
|
||||||
- refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
|
- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
||||||
|
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
||||||
|
- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
|
||||||
|
|
||||||
Minor Changes
|
Minor Changes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24 (https://github.com/ansible-collections/kubernetes.core/pull/520).
|
- Add integration test to check handling of module_defaults (https://github.com/ansible-collections/kubernetes.core/pull/296).
|
||||||
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
|
|
||||||
- added ignore.txt for Ansible 2.14 devel branch.
|
|
||||||
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
|
|
||||||
- helm - add support for -set-file, -set-json, -set and -set-string options when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
|
|
||||||
- helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
|
|
||||||
- helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
|
|
||||||
- helm - add support for timeout cli parameter to allow setting Helm timeout independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
|
|
||||||
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
|
|
||||||
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
|
|
||||||
- helm - when ansible is executed in check mode, return the diff between what's deployed and what will be deployed.
|
|
||||||
- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
|
|
||||||
- helm_info - add hooks, notes and manifest as part of returned information (https://github.com/ansible-collections/kubernetes.core/pull/546).
|
|
||||||
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
|
|
||||||
- helm_info - added possibility to get all values by adding get_all_values parameter (https://github.com/ansible-collections/kubernetes.core/pull/531).
|
|
||||||
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
|
|
||||||
- helm_plugin - Add support for helm plugin update using state=update.
|
|
||||||
- helm_repository - Ability to replace (overwrite) the repo if it already exists by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
|
|
||||||
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
|
|
||||||
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``, and ``ca_cert``.
|
|
||||||
- helm_repository - mark `pass_credentials` as no_log=True to silence false warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
|
|
||||||
- helm_template - add name (NAME of release) and disable_hook as optional module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- helm_template - add support for -set-file, -set-json, -set and -set-string options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
|
|
||||||
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
|
|
||||||
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
|
|
||||||
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
|
|
||||||
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
|
|
||||||
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
|
|
||||||
- k8s, k8s_scale, k8s_service - add support for resource definition as manifest via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
|
|
||||||
- k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod (https://github.com/ansible-collections/kubernetes.core/issues/486).
|
|
||||||
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
|
|
||||||
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
|
|
||||||
- k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
|
|
||||||
- k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
|
|
||||||
- k8s_log - add the ``all_containers`` for retrieving all containers' logs in the pod(s).
|
|
||||||
- k8s_log - added the `previous` parameter for retrieving the previously terminated pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
|
|
||||||
- k8s_log - added the `tail_lines` parameter to limit the number of lines to be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
|
|
||||||
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
|
|
||||||
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
|
|
||||||
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
|
|
||||||
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
|
|
||||||
|
|
||||||
Bugfixes
|
v2.2.2
|
||||||
--------
|
|
||||||
|
|
||||||
- Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to the client, add conditional check on kubernetes client version as this feature is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
|
|
||||||
- Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
|
|
||||||
- Helm - Fix issue with alternative kubeconfig provided with validate_certs=False (https://github.com/ansible-collections/kubernetes.core/issues/538).
|
|
||||||
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
|
|
||||||
- add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name (https://github.com/ansible-collections/kubernetes.core/issues/558).
|
|
||||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
|
||||||
- common - handle ``aliases`` passed from inventory and lookup plugins.
|
|
||||||
- helm_template - evaluate release_values after values_files, insuring highest precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
|
|
||||||
- import exception from ``kubernetes.client.rest``.
|
|
||||||
- k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
|
|
||||||
- k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
|
|
||||||
- k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
|
|
||||||
- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
|
||||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
|
||||||
- k8s_info - fix issue when module returns successful true after the resource cache has been established during periods where communication to the api-server is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
|
|
||||||
- k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
|
|
||||||
- k8s_log - fix exception raised when the name is not provided for resources requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
|
|
||||||
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
|
|
||||||
- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
|
|
||||||
- module_utils/k8s/client.py - fix issue when trying to authenticate with host, client_cert and client_key parameters only.
|
|
||||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
|
||||||
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
|
|
||||||
|
|
||||||
New Modules
|
|
||||||
-----------
|
|
||||||
|
|
||||||
- helm_pull - download a chart from a repository and (optionally) unpack it in local directory.
|
|
||||||
|
|
||||||
v2.3.1
|
|
||||||
======
|
======
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- Catch exception raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
|
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
||||||
- Remove `omit` placeholder when defining resource using template parameter (https://github.com/ansible-collections/kubernetes.core/issues/431).
|
|
||||||
- k8s - fix the issue when trying to delete resources using label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/433).
|
|
||||||
- k8s_cp - fix issue when using parameter local_path with file on managed node. (https://github.com/ansible-collections/kubernetes.core/issues/421).
|
|
||||||
- k8s_drain - fix error occurring when trying to drain node with disable_eviction set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
|
|
||||||
|
|
||||||
v2.3.0
|
v2.2.1
|
||||||
======
|
======
|
||||||
|
|
||||||
Minor Changes
|
|
||||||
-------------
|
|
||||||
|
|
||||||
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
|
|
||||||
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
|
|
||||||
- helm - add support for timeout cli parameter to allow setting Helm timeout independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
|
|
||||||
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
|
|
||||||
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
|
|
||||||
- helm - when ansible is executed in check mode, return the diff between what's deployed and what will be deployed.
|
|
||||||
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
|
|
||||||
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
|
|
||||||
- helm_plugin - Add support for helm plugin update using state=update.
|
|
||||||
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
|
|
||||||
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``, and ``ca_cert``.
|
|
||||||
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
|
|
||||||
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
|
|
||||||
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
|
|
||||||
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
|
|
||||||
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
|
|
||||||
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
|
|
||||||
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
|
|
||||||
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
|
|
||||||
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
|
|
||||||
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
|
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
|
|
||||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
||||||
- helm_template - evaluate release_values after values_files, insuring highest precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
|
|
||||||
- import exception from ``kubernetes.client.rest``.
|
|
||||||
- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
|
||||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
|
||||||
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
|
|
||||||
- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
|
|
||||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
|
||||||
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
|
|
||||||
|
|
||||||
New Modules
|
|
||||||
-----------
|
|
||||||
|
|
||||||
- k8s_taint - Taint a node in a Kubernetes/OpenShift cluster
|
|
||||||
|
|
||||||
v2.2.0
|
v2.2.0
|
||||||
======
|
======
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,5 +1,5 @@
|
|||||||
# Also needs to be updated in galaxy.yml
|
# Also needs to be updated in galaxy.yml
|
||||||
VERSION = 2.4.0
|
VERSION = 2.2.3
|
||||||
|
|
||||||
TEST_ARGS ?= ""
|
TEST_ARGS ?= ""
|
||||||
PYTHON_VERSION ?= `python -c 'import platform; print(".".join(platform.python_version_tuple()[0:2]))'`
|
PYTHON_VERSION ?= `python -c 'import platform; print(".".join(platform.python_version_tuple()[0:2]))'`
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -11,8 +11,6 @@ The collection includes a variety of Ansible content to help automate the manage
|
|||||||
|
|
||||||
This collection has been tested against following Ansible versions: **>=2.9.17**.
|
This collection has been tested against following Ansible versions: **>=2.9.17**.
|
||||||
|
|
||||||
For collections that support Ansible 2.9, please ensure you update your `network_os` to use the
|
|
||||||
fully qualified collection name (for example, `cisco.ios.ios`).
|
|
||||||
Plugins and modules within a collection may be tested with only specific Ansible versions.
|
Plugins and modules within a collection may be tested with only specific Ansible versions.
|
||||||
A collection may contain metadata that identifies these versions.
|
A collection may contain metadata that identifies these versions.
|
||||||
PEP440 is the schema used to describe the versions of Ansible.
|
PEP440 is the schema used to describe the versions of Ansible.
|
||||||
@@ -24,10 +22,6 @@ PEP440 is the schema used to describe the versions of Ansible.
|
|||||||
|
|
||||||
Note: Python2 is deprecated from [1st January 2020](https://www.python.org/doc/sunset-python-2/). Please switch to Python3.
|
Note: Python2 is deprecated from [1st January 2020](https://www.python.org/doc/sunset-python-2/). Please switch to Python3.
|
||||||
|
|
||||||
## Kubernetes Version Support
|
|
||||||
|
|
||||||
This collection supports Kubernetes versions >=1.19.
|
|
||||||
|
|
||||||
## Included content
|
## Included content
|
||||||
|
|
||||||
Click on the name of a plugin or module to view that content's documentation:
|
Click on the name of a plugin or module to view that content's documentation:
|
||||||
@@ -61,7 +55,6 @@ Name | Description
|
|||||||
[kubernetes.core.helm_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_info_module.rst)|Get information from Helm package deployed inside the cluster
|
[kubernetes.core.helm_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_info_module.rst)|Get information from Helm package deployed inside the cluster
|
||||||
[kubernetes.core.helm_plugin](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_module.rst)|Manage Helm plugins
|
[kubernetes.core.helm_plugin](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_module.rst)|Manage Helm plugins
|
||||||
[kubernetes.core.helm_plugin_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_info_module.rst)|Gather information about Helm plugins
|
[kubernetes.core.helm_plugin_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_info_module.rst)|Gather information about Helm plugins
|
||||||
[kubernetes.core.helm_pull](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_pull_module.rst)|download a chart from a repository and (optionally) unpack it in local directory.
|
|
||||||
[kubernetes.core.helm_repository](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_repository_module.rst)|Manage Helm repositories.
|
[kubernetes.core.helm_repository](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_repository_module.rst)|Manage Helm repositories.
|
||||||
[kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates
|
[kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates
|
||||||
[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_module.rst)|Manage Kubernetes (K8s) objects
|
[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_module.rst)|Manage Kubernetes (K8s) objects
|
||||||
@@ -75,7 +68,6 @@ Name | Description
|
|||||||
[kubernetes.core.k8s_rollback](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_rollback_module.rst)|Rollback Kubernetes (K8S) Deployments and DaemonSets
|
[kubernetes.core.k8s_rollback](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_rollback_module.rst)|Rollback Kubernetes (K8S) Deployments and DaemonSets
|
||||||
[kubernetes.core.k8s_scale](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_scale_module.rst)|Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
|
[kubernetes.core.k8s_scale](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_scale_module.rst)|Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
|
||||||
[kubernetes.core.k8s_service](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_service_module.rst)|Manage Services on Kubernetes
|
[kubernetes.core.k8s_service](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_service_module.rst)|Manage Services on Kubernetes
|
||||||
[kubernetes.core.k8s_taint](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_taint_module.rst)|Taint a node in a Kubernetes/OpenShift cluster
|
|
||||||
|
|
||||||
<!--end collection content-->
|
<!--end collection content-->
|
||||||
|
|
||||||
@@ -93,7 +85,7 @@ You can also include it in a `requirements.yml` file and install it via `ansible
|
|||||||
---
|
---
|
||||||
collections:
|
collections:
|
||||||
- name: kubernetes.core
|
- name: kubernetes.core
|
||||||
version: 2.4.0
|
version: 2.2.3
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installing the Kubernetes Python Library
|
### Installing the Kubernetes Python Library
|
||||||
@@ -173,7 +165,7 @@ For documentation on how to use individual modules and other content included in
|
|||||||
## Ansible Turbo mode Tech Preview
|
## Ansible Turbo mode Tech Preview
|
||||||
|
|
||||||
|
|
||||||
The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode for modules, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
|
The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
@@ -184,9 +176,6 @@ The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
To enable Turbo mode for k8s lookup plugin, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. This is not working when
|
|
||||||
defined in the playbook using `environment` keyword as above, you must set it using `export ENABLE_TURBO_MODE=1`.
|
|
||||||
|
|
||||||
Please read more about Ansible Turbo mode - [here](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/ansible_turbo_mode.rst).
|
Please read more about Ansible Turbo mode - [here](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/ansible_turbo_mode.rst).
|
||||||
|
|
||||||
## Testing and Development
|
## Testing and Development
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
kubernetes-client [platform:fedora]
|
kubernetes-client [platform:fedora]
|
||||||
openshift-clients [platform:rhel-8]
|
|
||||||
openshift-clients [platform:rhel-9]
|
|
||||||
|
|||||||
@@ -486,279 +486,35 @@ releases:
|
|||||||
name: kustomize
|
name: kustomize
|
||||||
namespace: null
|
namespace: null
|
||||||
release_date: '2021-09-15'
|
release_date: '2021-09-15'
|
||||||
2.3.0:
|
2.2.1:
|
||||||
changes:
|
changes:
|
||||||
bugfixes:
|
bugfixes:
|
||||||
- Various modules and plugins - use vendored version of ``distutils.version``
|
|
||||||
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
|
|
||||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
||||||
- helm_template - evaluate release_values after values_files, insuring highest
|
fragments:
|
||||||
precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
|
- 0-copy_ignore_txt.yml
|
||||||
- import exception from ``kubernetes.client.rest``.
|
- _wait_for_label_selector_optional.yaml
|
||||||
|
release_date: '2021-10-18'
|
||||||
|
2.2.2:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
||||||
|
fragments:
|
||||||
|
- 298-remove-binary-file.yaml
|
||||||
|
release_date: '2021-12-07'
|
||||||
|
2.2.3:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
- k8s_drain - fix error caused by accessing an undefined variable when pods
|
- k8s_drain - fix error caused by accessing an undefined variable when pods
|
||||||
have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
||||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
||||||
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
|
|
||||||
- module_utils.common - change default opening mode to read-bytes to avoid bad
|
- module_utils.common - change default opening mode to read-bytes to avoid bad
|
||||||
interpretation of non ascii characters and strings, often present in 3rd party
|
interpretation of non ascii characters and strings, often present in 3rd party
|
||||||
manifests.
|
manifests.
|
||||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
|
||||||
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
|
|
||||||
minor_changes:
|
minor_changes:
|
||||||
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
|
- Add integration test to check handling of module_defaults
|
||||||
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
|
(https://github.com/ansible-collections/kubernetes.core/pull/296).
|
||||||
- helm - add support for timeout cli parameter to allow setting Helm timeout
|
|
||||||
independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
|
|
||||||
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
|
|
||||||
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
|
|
||||||
- helm - when ansible is executed in check mode, return the diff between what's
|
|
||||||
deployed and what will be deployed.
|
|
||||||
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
|
|
||||||
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
|
|
||||||
- helm_plugin - Add support for helm plugin update using state=update.
|
|
||||||
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
|
|
||||||
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``,
|
|
||||||
and ``ca_cert``.
|
|
||||||
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
|
|
||||||
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
|
|
||||||
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
|
|
||||||
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
|
|
||||||
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
|
|
||||||
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options``
|
|
||||||
to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
|
|
||||||
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
|
|
||||||
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
|
|
||||||
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
|
|
||||||
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
|
|
||||||
fragments:
|
fragments:
|
||||||
- 0-copy_ignore_txt.yml
|
|
||||||
- 226-add-version-parameter-to-helm_plugin.yml
|
|
||||||
- 231-helm-add-timeout-parameter.yaml
|
|
||||||
- 238-helm-add-support-for-helm-uninstall-wait.yaml
|
|
||||||
- 238-k8s-add-support-for-generate_name.yml
|
|
||||||
- 245-add-dry-run.yaml
|
|
||||||
- 250-k8s-add-support-for-impersonation.yaml
|
|
||||||
- 253-dont-wait-on-list-resources.yaml
|
- 253-dont-wait-on-list-resources.yaml
|
||||||
- 255-k8s_scale-k8s_rollback-add-support-for-check_mode.yml
|
|
||||||
- 260-k8s-add-support-for-server_side_apply.yml
|
|
||||||
- 272-k8s-add-support-no_proxy.yaml
|
|
||||||
- 282-helm-repository-add-pass-credentials.yaml
|
|
||||||
- 290-returns-diff-in-check-mode.yaml
|
|
||||||
- 295-fix-k8s-drain-variable-declaration.yaml
|
- 295-fix-k8s-drain-variable-declaration.yaml
|
||||||
- 298-remove-binary-file.yaml
|
|
||||||
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
|
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
|
||||||
- 313-helm-template-add-support-for-show-only-and-release-namespace.yml
|
release_date: '2022-01-18'
|
||||||
- 321-kubectl_sleep.yml
|
|
||||||
- 322-Add-delete_emptydir_data-to-drain-delete_options.yaml
|
|
||||||
- 335-k8s-lookup-add-support-for-turbo-mode.yml
|
|
||||||
- 347-routing.yml
|
|
||||||
- 348-helm_template-fix-precedence-of-release-values-over-values-files.yaml
|
|
||||||
- 358-k8s_exec.yml
|
|
||||||
- 364-use-resource-prefix.yaml
|
|
||||||
- 377-helm-info-state.yml
|
|
||||||
- 389-helm-add-support-chart_repo_url-on-helm_diff.yml
|
|
||||||
- 391-fix-statefulset-wait.yaml
|
|
||||||
- _wait_for_label_selector_optional.yaml
|
|
||||||
- disutils.version.yml
|
|
||||||
- exception.yml
|
|
||||||
- helm_repository.yml
|
|
||||||
modules:
|
|
||||||
- description: Taint a node in a Kubernetes/OpenShift cluster
|
|
||||||
name: k8s_taint
|
|
||||||
namespace: ''
|
|
||||||
release_date: '2022-03-11'
|
|
||||||
2.3.1:
|
|
||||||
changes:
|
|
||||||
bugfixes:
|
|
||||||
- Catch expectation raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
|
|
||||||
- Remove `omit` placeholder when defining resource using template parameter
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/issues/431).
|
|
||||||
- k8s - fix the issue when trying to delete resources using label_selectors
|
|
||||||
options (https://github.com/ansible-collections/kubernetes.core/issues/433).
|
|
||||||
- k8s_cp - fix issue when using parameter local_path with file on managed node.
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/issues/421).
|
|
||||||
- k8s_drain - fix error occurring when trying to drain node with disable_eviction
|
|
||||||
set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
|
|
||||||
fragments:
|
|
||||||
- 408-fix-wait-on-exception.yml
|
|
||||||
- 417-fix-k8s-drain-delete-options.yaml
|
|
||||||
- 422-k8s_cp-fix-issue-when-issue-local_path.yaml
|
|
||||||
- 432-fix-issue-when-using-template-parameter.yaml
|
|
||||||
- 434-fix-k8s-delete-using-label_selector.yaml
|
|
||||||
release_date: '2022-05-02'
|
|
||||||
2.4.0:
|
|
||||||
changes:
|
|
||||||
bugfixes:
|
|
||||||
- Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to
|
|
||||||
the client, add conditional check on kubernetes client version as this feature
|
|
||||||
is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
|
|
||||||
- Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
|
|
||||||
- Helm - Fix issue with alternative kubeconfig provided with validate_certs=False
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/issues/538).
|
|
||||||
- Various modules and plugins - use vendored version of ``distutils.version``
|
|
||||||
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
|
|
||||||
- add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/issues/558).
|
|
||||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
|
||||||
- common - handle ``aliases`` passed from inventory and lookup plugins.
|
|
||||||
- helm_template - evaluate release_values after values_files, insuring highest
|
|
||||||
precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
|
|
||||||
- import exception from ``kubernetes.client.rest``.
|
|
||||||
- k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
|
|
||||||
- k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
|
|
||||||
- k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
|
|
||||||
- k8s_drain - fix error caused by accessing an undefined variable when pods
|
|
||||||
have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
|
||||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
|
||||||
- k8s_info - fix issue when module returns successful true after the resource
|
|
||||||
cache has been established during periods where communication to the api-server
|
|
||||||
is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
|
|
||||||
- k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
|
|
||||||
- k8s_log - fix exception raised when the name is not provided for resources
|
|
||||||
requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
|
|
||||||
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
|
|
||||||
- module_utils.common - change default opening mode to read-bytes to avoid bad
|
|
||||||
interpretation of non ascii characters and strings, often present in 3rd party
|
|
||||||
manifests.
|
|
||||||
- module_utils/k8s/client.py - fix issue when trying to authenticate with host,
|
|
||||||
client_cert and client_key parameters only.
|
|
||||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
|
||||||
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
|
|
||||||
major_changes:
|
|
||||||
- refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
|
|
||||||
minor_changes:
|
|
||||||
- Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/pull/520).
|
|
||||||
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
|
|
||||||
- added ignore.txt for Ansible 2.14 devel branch.
|
|
||||||
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
|
|
||||||
- helm - add support for -set-file, -set-json, -set and -set-string options
|
|
||||||
when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
|
|
||||||
- helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
|
|
||||||
- helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
|
|
||||||
- helm - add support for timeout cli parameter to allow setting Helm timeout
|
|
||||||
independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
|
|
||||||
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
|
|
||||||
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
|
|
||||||
- helm - when ansible is executed in check mode, return the diff between what's
|
|
||||||
deployed and what will be deployed.
|
|
||||||
- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for
|
|
||||||
in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
|
|
||||||
- helm_info - add hooks, notes and manifest as part of returned information
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/pull/546).
|
|
||||||
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
|
|
||||||
- helm_info - added possibility to get all values by adding get_all_values parameter
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/pull/531).
|
|
||||||
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
|
|
||||||
- helm_plugin - Add support for helm plugin update using state=update.
|
|
||||||
- helm_repository - Ability to replace (overwrite) the repo if it already exists
|
|
||||||
by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
|
|
||||||
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
|
|
||||||
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``,
|
|
||||||
and ``ca_cert``.
|
|
||||||
- helm_repository - mark `pass_credentials` as no_log=True to silence false
|
|
||||||
warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
|
|
||||||
- helm_template - add name (NAME of release) and disable_hook as optional module
|
|
||||||
arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
|
|
||||||
- helm_template - add support for -set-file, -set-json, -set and -set-string
|
|
||||||
options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
|
|
||||||
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
|
|
||||||
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
|
|
||||||
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
|
|
||||||
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
|
|
||||||
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
|
|
||||||
- k8s, k8s_scale, k8s_service - add support for resource definition as manifest
|
|
||||||
via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
|
|
||||||
- k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod
|
|
||||||
(https://github.com/ansible-collections/kubernetes.core/issues/486).
|
|
||||||
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options``
|
|
||||||
to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
|
|
||||||
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
|
|
||||||
- k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
|
|
||||||
- k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
|
|
||||||
- k8s_log - add the ``all_containers`` for retrieving all containers' logs in
|
|
||||||
the pod(s).
|
|
||||||
- k8s_log - added the `previous` parameter for retrieving the previously terminated
|
|
||||||
pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
|
|
||||||
- k8s_log - added the `tail_lines` parameter to limit the number of lines to
|
|
||||||
be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
|
|
||||||
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
|
|
||||||
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
|
|
||||||
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
|
|
||||||
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in
|
|
||||||
the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
|
|
||||||
fragments:
|
|
||||||
- 0-copy_ignore_txt.yml
|
|
||||||
- 208-add-dependency-update.yaml
|
|
||||||
- 226-add-version-parameter-to-helm_plugin.yml
|
|
||||||
- 231-helm-add-timeout-parameter.yaml
|
|
||||||
- 238-helm-add-support-for-helm-uninstall-wait.yaml
|
|
||||||
- 238-k8s-add-support-for-generate_name.yml
|
|
||||||
- 245-add-dry-run.yaml
|
|
||||||
- 250-k8s-add-support-for-impersonation.yaml
|
|
||||||
- 253-dont-wait-on-list-resources.yaml
|
|
||||||
- 255-k8s_scale-k8s_rollback-add-support-for-check_mode.yml
|
|
||||||
- 260-k8s-add-support-for-server_side_apply.yml
|
|
||||||
- 272-k8s-add-support-no_proxy.yaml
|
|
||||||
- 282-helm-repository-add-pass-credentials.yaml
|
|
||||||
- 290-returns-diff-in-check-mode.yaml
|
|
||||||
- 295-fix-k8s-drain-variable-declaration.yaml
|
|
||||||
- 298-remove-binary-file.yaml
|
|
||||||
- 30-helm-add-post-renderer-support.yml
|
|
||||||
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
|
|
||||||
- 313-helm-template-add-support-for-name-and-disablehook.yml
|
|
||||||
- 313-helm-template-add-support-for-show-only-and-release-namespace.yml
|
|
||||||
- 321-kubectl_sleep.yml
|
|
||||||
- 322-Add-delete_emptydir_data-to-drain-delete_options.yaml
|
|
||||||
- 335-k8s-lookup-add-support-for-turbo-mode.yml
|
|
||||||
- 347-routing.yml
|
|
||||||
- 348-helm_template-fix-precedence-of-release-values-over-values-files.yaml
|
|
||||||
- 358-k8s_exec.yml
|
|
||||||
- 364-use-resource-prefix.yaml
|
|
||||||
- 377-helm-info-state.yml
|
|
||||||
- 389-helm-add-support-chart_repo_url-on-helm_diff.yml
|
|
||||||
- 391-fix-statefulset-wait.yaml
|
|
||||||
- 411_k8s_json_patch.yml
|
|
||||||
- 412_pass_creds.yml
|
|
||||||
- 417_deprecation.yml
|
|
||||||
- 428-fix-kubeconfig-parameter-with-multiple-config-files.yaml
|
|
||||||
- 437-k8s-add-support-for-previous-logs.yaml
|
|
||||||
- 456-replace-distutils.yml
|
|
||||||
- 478-add-support-for-manifest-url.yaml
|
|
||||||
- 481-refactor-common.yml
|
|
||||||
- 488-add-support-for-tail-logs.yaml
|
|
||||||
- 493-k8s_log-fix-module-when-pod-does-exist.yaml
|
|
||||||
- 497-helm-add-support-for-in-memory-kubeconfig.yml
|
|
||||||
- 498-k8s-honor-aliases.yaml
|
|
||||||
- 505-add-from-yaml-all-example.yml
|
|
||||||
- 509-helm-repo-add-force_update-argument.yaml
|
|
||||||
- 512-k8s_cp-add-support-for-check_mode-update-command-for-listing-files-into-pod.yaml
|
|
||||||
- 515-update-sanity-for-2-15.yml
|
|
||||||
- 522-fix-helm-tests.yml
|
|
||||||
- 523-helm_info-get-all-values.yaml
|
|
||||||
- 528-k8s_log-support-all_containers-options.yml
|
|
||||||
- 532-k8s_crd-fix-integration-test.yml
|
|
||||||
- 546-helm-install-add-support-for-set-options.yaml
|
|
||||||
- 549-fix-server-side-apply.yaml
|
|
||||||
- 552-k8s_cp-fix-issue-when-copying-item-with-space-in-its-name.yml
|
|
||||||
- 561-fix-dry-run.yml
|
|
||||||
- 562-helm-fix-issue-when-alternative-kubeconfig-is-provided.yaml
|
|
||||||
- 571-k8s_info-fix-issue-with-api-server.yaml
|
|
||||||
- _wait_for_label_selector_optional.yaml
|
|
||||||
- disutils.version.yml
|
|
||||||
- exception.yml
|
|
||||||
- fix-ci-unit-tests.yaml
|
|
||||||
- helm_repository.yml
|
|
||||||
- ignore_2.14.yml
|
|
||||||
- k8s_config_resource_name-add-missing-documentation.yml
|
|
||||||
- k8s_rollback_reduce_tmeouts.yaml
|
|
||||||
- k8s_user_impersonation_k8s_1_24.yaml
|
|
||||||
- minor-tests-duration.yaml
|
|
||||||
modules:
|
|
||||||
- description: download a chart from a repository and (optionally) unpack it in
|
|
||||||
local directory.
|
|
||||||
name: helm_pull
|
|
||||||
namespace: ''
|
|
||||||
release_date: '2023-01-24'
|
|
||||||
|
|||||||
2
changelogs/fragments/456-replace-distutils.yml
Normal file
2
changelogs/fragments/456-replace-distutils.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
minor_changes:
|
||||||
|
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
|
||||||
2
changelogs/fragments/disutils.version.yml
Normal file
2
changelogs/fragments/disutils.version.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bugfixes:
|
||||||
|
- "Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314)."
|
||||||
3
changelogs/fragments/exception.yml
Normal file
3
changelogs/fragments/exception.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
bugfixes:
|
||||||
|
- import exception from ``kubernetes.client.rest``.
|
||||||
@@ -54,20 +54,20 @@ In this use case / example, we will create a Pod in the given Kubernetes Cluster
|
|||||||
- kubernetes.core
|
- kubernetes.core
|
||||||
tasks:
|
tasks:
|
||||||
- name: Create a pod
|
- name: Create a pod
|
||||||
kubernetes.core.k8s:
|
k8s:
|
||||||
state: present
|
state: present
|
||||||
definition:
|
definition:
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: "utilitypod-1"
|
name: "utilitypod-1"
|
||||||
namespace: default
|
namespace: default
|
||||||
labels:
|
labels:
|
||||||
app: galaxy
|
app: galaxy
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: utilitypod
|
- name: utilitypod
|
||||||
image: busybox
|
image: busybox
|
||||||
|
|
||||||
Since Ansible utilizes the Kubernetes API to perform actions, in this use case we will be connecting directly to the Kubernetes cluster.
|
Since Ansible utilizes the Kubernetes API to perform actions, in this use case we will be connecting directly to the Kubernetes cluster.
|
||||||
|
|
||||||
|
|||||||
@@ -193,30 +193,6 @@ Parameters
|
|||||||
<div>Create the release namespace if not present.</div>
|
<div>Create the release namespace if not present.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>dependency_update</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Run standelone <code>helm dependency update CHART</code> before the operation.</div>
|
|
||||||
<div>Run inline <code>--dependency-update</code> with <code>helm install</code> command. This feature is not supported yet with the <code>helm upgrade</code> command.</div>
|
|
||||||
<div>So we should consider to use <em>dependency_update</em> options with <em>replace</em> option enabled when specifying <em>chart_repo_url</em>.</div>
|
|
||||||
<div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
|
|
||||||
<div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -437,24 +413,6 @@ Parameters
|
|||||||
<div>Skip custom resource definitions when installing or upgrading.</div>
|
<div>Skip custom resource definitions when installing or upgrading.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>timeout</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>A Go duration (described here <em>https://pkg.go.dev/time#ParseDuration</em>) value to wait for Kubernetes commands to complete. This defaults to 5m0s.</div>
|
|
||||||
<div>similar to <code>wait_timeout</code> but does not required <code>wait</code> to be activated.</div>
|
|
||||||
<div>Mutually exclusive with <code>wait_timeout</code>.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -471,7 +429,7 @@ Parameters
|
|||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>Run <code>helm repo update</code> before the operation. Can be run as part of the package installation or as a separate step (see Examples).</div>
|
<div>Run <code>helm repo update</code> before the operation. Can be run as part of the package installation or as a separate step.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -532,8 +490,7 @@ Parameters
|
|||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>When <em>release_state</em> is set to <code>present</code>, wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.</div>
|
<div>Wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.</div>
|
||||||
<div>When <em>release_state</em> is set to <code>absent</code>, will wait until all the resources are deleted before returning. It will wait for as long as <em>wait_timeout</em>. This feature requires helm>=3.7.0. Added in version 2.3.0.</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -549,7 +506,6 @@ Parameters
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).</div>
|
<div>Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).</div>
|
||||||
<div>The use of <em>wait_timeout</em> to wait for kubernetes commands to complete has been deprecated and will be removed after 2022-12-01.</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -605,13 +561,6 @@ Examples
|
|||||||
state: absent
|
state: absent
|
||||||
wait: true
|
wait: true
|
||||||
|
|
||||||
- name: Separately update the repository cache
|
|
||||||
kubernetes.core.helm:
|
|
||||||
name: dummy
|
|
||||||
namespace: kube-system
|
|
||||||
state: absent
|
|
||||||
update_repo_cache: true
|
|
||||||
|
|
||||||
# From git
|
# From git
|
||||||
- name: Git clone stable repo on HEAD
|
- name: Git clone stable repo on HEAD
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
|
|||||||
@@ -1,467 +0,0 @@
|
|||||||
.. _kubernetes.core.helm_pull_module:
|
|
||||||
|
|
||||||
|
|
||||||
*************************
|
|
||||||
kubernetes.core.helm_pull
|
|
||||||
*************************
|
|
||||||
|
|
||||||
**download a chart from a repository and (optionally) unpack it in local directory.**
|
|
||||||
|
|
||||||
|
|
||||||
Version added: 2.4.0
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
:depth: 1
|
|
||||||
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
- Retrieve a package from a package repository, and download it locally.
|
|
||||||
- It can also be used to perform cryptographic verification of a chart without installing the chart.
|
|
||||||
- There are options for unpacking the chart after download.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
The below requirements are needed on the host that executes this module.
|
|
||||||
|
|
||||||
- helm >= 3.0 (https://github.com/helm/helm/releases)
|
|
||||||
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
|
||||||
<tr>
|
|
||||||
<th colspan="1">Parameter</th>
|
|
||||||
<th>Choices/<font color="blue">Defaults</font></th>
|
|
||||||
<th width="100%">Comments</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>binary_path</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The path of a helm binary to use.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_ca_cert</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Verify certificates of HTTPS-enabled servers using this CA bundle.</div>
|
|
||||||
<div>Requires helm >= 3.1.0.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_devel</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li>no</li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Use development versions, too. Equivalent to version '>0.0.0-0'.</div>
|
|
||||||
<div>Mutually exclusive with <code>chart_version</code>.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_ref</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
/ <span style="color: red">required</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>chart name on chart repository.</div>
|
|
||||||
<div>absolute URL.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_ssl_cert_file</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Identify HTTPS client using this SSL certificate file.</div>
|
|
||||||
<div>Requires helm >= 3.1.0.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_ssl_key_file</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Identify HTTPS client using this SSL key file</div>
|
|
||||||
<div>Requires helm >= 3.1.0.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>chart_version</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Specify a version constraint for the chart version to use.</div>
|
|
||||||
<div>This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).</div>
|
|
||||||
<div>Mutually exclusive with <code>chart_devel</code>.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>destination</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
/ <span style="color: red">required</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>location to write the chart.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>pass_credentials</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Pass credentials to all domains.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>provenance</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Fetch the provenance file, but don't perform verification.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>repo_password</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Chart repository password where to locate the requested chart.</div>
|
|
||||||
<div>Required if <code>repo_username</code> is specified.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: password, chart_repo_password</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>repo_url</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>chart repository url where to locate the requested chart.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: url, chart_repo_url</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>repo_username</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Chart repository username where to locate the requested chart.</div>
|
|
||||||
<div>Required if <code>repo_password</code> is specified.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: username, chart_repo_username</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>skip_tls_certs_check</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Whether or not to check tls certificate for the chart download.</div>
|
|
||||||
<div>Requires helm >= 3.3.0.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>untar_chart</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>if set to true, will untar the chart after downloading it.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>verify_chart</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Verify the package before using it.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>verify_chart_keyring</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>location of public keys used for verification.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
- name: Download chart using chart url
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
|
|
||||||
destination: /path/to/chart
|
|
||||||
|
|
||||||
- name: Download Chart using chart_name and repo_url
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
|
|
||||||
- name: Download Chart (skip tls certificate check)
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
skip_tls_certs_check: yes
|
|
||||||
|
|
||||||
- name: Download Chart using chart registry credentials
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
username: myuser
|
|
||||||
password: mypassword123
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Return Values
|
|
||||||
-------------
|
|
||||||
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
|
||||||
<tr>
|
|
||||||
<th colspan="1">Key</th>
|
|
||||||
<th>Returned</th>
|
|
||||||
<th width="100%">Description</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>command</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>always</td>
|
|
||||||
<td>
|
|
||||||
<div>Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
|
|
||||||
<br/>
|
|
||||||
<div style="font-size: smaller"><b>Sample:</b></div>
|
|
||||||
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm pull --repo test ...</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>rc</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">integer</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>always</td>
|
|
||||||
<td>
|
|
||||||
<div>Helm pull command return code</div>
|
|
||||||
<br/>
|
|
||||||
<div style="font-size: smaller"><b>Sample:</b></div>
|
|
||||||
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">1</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>stderr</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>always</td>
|
|
||||||
<td>
|
|
||||||
<div>Full `helm pull` command stderr, in case you want to display it or examine the event log</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>stdout</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>always</td>
|
|
||||||
<td>
|
|
||||||
<div>Full `helm pull` command stdout, in case you want to display it or examine the event log</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
|
|
||||||
Status
|
|
||||||
------
|
|
||||||
|
|
||||||
|
|
||||||
Authors
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
- Aubin Bikouo (@abikouo)
|
|
||||||
@@ -96,28 +96,6 @@ Parameters
|
|||||||
<div>Chart version to use. If this is not specified, the latest version is installed.</div>
|
<div>Chart version to use. If this is not specified, the latest version is installed.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>dependency_update</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Run helm dependency update before the operation.</div>
|
|
||||||
<div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
|
|
||||||
<div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -136,41 +136,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -167,41 +167,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -140,26 +140,6 @@ Parameters
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="elbow-placeholder"></td>
|
<td class="elbow-placeholder"></td>
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>delete_emptydir_data</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained)</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
<b>disable_eviction</b>
|
<b>disable_eviction</b>
|
||||||
@@ -286,41 +266,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -166,41 +166,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -173,41 +173,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -155,41 +155,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -174,41 +174,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -351,41 +351,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="3">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -172,41 +172,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -189,41 +189,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -172,41 +172,6 @@ Parameters
|
|||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -1,660 +0,0 @@
|
|||||||
.. _kubernetes.core.k8s_taint_module:
|
|
||||||
|
|
||||||
|
|
||||||
*************************
|
|
||||||
kubernetes.core.k8s_taint
|
|
||||||
*************************
|
|
||||||
|
|
||||||
**Taint a node in a Kubernetes/OpenShift cluster**
|
|
||||||
|
|
||||||
|
|
||||||
Version added: 2.3.0
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
:depth: 1
|
|
||||||
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
|
|
||||||
- Untaint will remove taints from nodes as needed.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
The below requirements are needed on the host that executes this module.
|
|
||||||
|
|
||||||
- python >= 3.6
|
|
||||||
- kubernetes >= 12.0.0
|
|
||||||
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
|
||||||
<tr>
|
|
||||||
<th colspan="2">Parameter</th>
|
|
||||||
<th>Choices/<font color="blue">Defaults</font></th>
|
|
||||||
<th width="100%">Comments</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>api_key</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>ca_cert</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>client_cert</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>client_key</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">path</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>context</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>host</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_groups</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Group(s) to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>impersonate_user</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Username to impersonate for the operation.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>kubeconfig</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">raw</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
|
||||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>name</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
/ <span style="color: red">required</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The name of the node.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>no_proxy</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The comma separated list of hosts/domains/IP/CIDR that shouldn't go through proxy. Can also be specified via K8S_AUTH_NO_PROXY environment variable.</div>
|
|
||||||
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. NO_PROXY).</div>
|
|
||||||
<div>This feature requires kubernetes>=19.15.0. When kubernetes library is less than 19.15.0, it fails even no_proxy set in correct.</div>
|
|
||||||
<div>example value is "localhost,.local,.example.com,127.0.0.1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>password</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
|
|
||||||
<div>Please read the description of the <code>username</code> option for a discussion of when this option is applicable.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>persist_config</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li>no</li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Whether or not to save the kube config refresh tokens. Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.</div>
|
|
||||||
<div>When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the new refresh token to the kube config file.</div>
|
|
||||||
<div>Default to false.</div>
|
|
||||||
<div>Please note that the current version of the k8s python client library does not support setting this flag to True yet.</div>
|
|
||||||
<div>The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>proxy</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.</div>
|
|
||||||
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>proxy_headers</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">dictionary</span>
|
|
||||||
</div>
|
|
||||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The Header used for the HTTP proxy.</div>
|
|
||||||
<div>Documentation can be found here <a href='https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers'>https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers</a>.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>basic_auth</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Colon-separated username:password for basic authentication header.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>proxy_basic_auth</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Colon-separated username:password for proxy basic authentication header.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>user_agent</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>String representing the user-agent you want, such as foo/1.0.</div>
|
|
||||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>replace</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>no</b> ←</div></li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>If <code>true</code>, allow taints to be replaced.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>state</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li><div style="color: blue"><b>present</b> ←</div></li>
|
|
||||||
<li>absent</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Determines whether to add or remove taints.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>taints</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">list</span>
|
|
||||||
/ <span style="color: purple">elements=dictionary</span>
|
|
||||||
/ <span style="color: red">required</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>List containing the taints.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>effect</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li>NoSchedule</li>
|
|
||||||
<li>NoExecute</li>
|
|
||||||
<li>PreferNoSchedule</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The effect of the taint on Pods that do not tolerate the taint.</div>
|
|
||||||
<div>Required when <em>state=present</em>.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>key</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The taint key to be applied to a node.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"></td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>value</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>The taint value corresponding to the taint key.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>username</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
|
|
||||||
<div>Please note that this only works with clusters configured to use HTTP Basic Auth. If your cluster has a different form of authentication (e.g. OAuth2 in OpenShift), this option will not work as expected and you should look into the <span class='module'>community.okd.k8s_auth</span> module, as that might do what you need.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
|
||||||
<b>validate_certs</b>
|
|
||||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">boolean</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
|
||||||
<li>no</li>
|
|
||||||
<li>yes</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
|
|
||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
|
||||||
Notes
|
|
||||||
-----
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
- To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
- name: Taint node "foo"
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: present
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
|
|
||||||
- name: Taint node "foo"
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: present
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
- effect: NoSchedule
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
|
|
||||||
- name: Remove taint from "foo".
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: absent
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Return Values
|
|
||||||
-------------
|
|
||||||
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
|
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
|
||||||
<tr>
|
|
||||||
<th colspan="2">Key</th>
|
|
||||||
<th>Returned</th>
|
|
||||||
<th width="100%">Description</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>result</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">complex</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>The tainted Node object. Will be empty in the case of a deletion.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"> </td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>api_version</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>The versioned schema of this representation of an object.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"> </td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>kind</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">string</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>Represents the REST resource this object represents.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"> </td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>metadata</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">complex</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>Standard object metadata. Includes name, namespace, annotations, labels, etc.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"> </td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>spec</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">complex</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>Specific attributes of the object. Will vary based on the <em>api_version</em> and <em>kind</em>.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="elbow-placeholder"> </td>
|
|
||||||
<td colspan="1">
|
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
|
||||||
<b>status</b>
|
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
|
||||||
<div style="font-size: small">
|
|
||||||
<span style="color: purple">complex</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>success</td>
|
|
||||||
<td>
|
|
||||||
<div>Current status details for the object.</div>
|
|
||||||
<br/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
|
|
||||||
Status
|
|
||||||
------
|
|
||||||
|
|
||||||
|
|
||||||
Authors
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
- Alina Buzachis (@alinabuzachis)
|
|
||||||
@@ -25,7 +25,7 @@ tags:
|
|||||||
- openshift
|
- openshift
|
||||||
- okd
|
- okd
|
||||||
- cluster
|
- cluster
|
||||||
version: 2.4.0
|
version: 2.2.3
|
||||||
build_ignore:
|
build_ignore:
|
||||||
- .DS_Store
|
- .DS_Store
|
||||||
- '*.tar.gz'
|
- '*.tar.gz'
|
||||||
|
|||||||
@@ -17,6 +17,37 @@ action_groups:
|
|||||||
- k8s_drain
|
- k8s_drain
|
||||||
|
|
||||||
plugin_routing:
|
plugin_routing:
|
||||||
|
action:
|
||||||
|
helm:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
helm_info:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
helm_plugin:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
helm_plugin_info:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
helm_repository:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_cluster_info:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_cp:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_drain:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_event_info:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_exec:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_log:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_rollback:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_scale:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
|
k8s_service:
|
||||||
|
redirect: kubernetes.core.k8s_info
|
||||||
inventory:
|
inventory:
|
||||||
openshift:
|
openshift:
|
||||||
redirect: community.okd.openshift
|
redirect: community.okd.openshift
|
||||||
|
|||||||
@@ -3,57 +3,23 @@
|
|||||||
# Copyright (c) 2020, Ansible Project
|
# Copyright (c) 2020, Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import platform
|
|
||||||
|
|
||||||
from ansible.config.manager import ensure_type
|
from ansible.config.manager import ensure_type
|
||||||
from ansible.errors import (
|
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
|
||||||
AnsibleError,
|
|
||||||
AnsibleFileNotFound,
|
|
||||||
AnsibleAction,
|
|
||||||
AnsibleActionFail,
|
|
||||||
)
|
|
||||||
from ansible.module_utils.parsing.convert_bool import boolean
|
from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
from ansible.module_utils.six import string_types, iteritems
|
from ansible.module_utils.six import string_types, iteritems
|
||||||
from ansible.module_utils._text import to_text, to_bytes, to_native
|
from ansible.module_utils._text import to_text, to_bytes, to_native
|
||||||
from ansible.plugins.action import ActionBase
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
|
|
||||||
class RemoveOmit(object):
|
|
||||||
def __init__(self, buffer, omit_value):
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
except ImportError:
|
|
||||||
raise AnsibleError("Failed to import the required Python library (PyYAML).")
|
|
||||||
self.data = yaml.safe_load_all(buffer)
|
|
||||||
self.omit = omit_value
|
|
||||||
|
|
||||||
def remove_omit(self, data):
|
|
||||||
if isinstance(data, dict):
|
|
||||||
result = dict()
|
|
||||||
for key, value in iteritems(data):
|
|
||||||
if value == self.omit:
|
|
||||||
continue
|
|
||||||
result[key] = self.remove_omit(value)
|
|
||||||
return result
|
|
||||||
if isinstance(data, list):
|
|
||||||
return [self.remove_omit(v) for v in data if v != self.omit]
|
|
||||||
return data
|
|
||||||
|
|
||||||
def output(self):
|
|
||||||
return [self.remove_omit(d) for d in self.data]
|
|
||||||
|
|
||||||
|
|
||||||
ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
|
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
class ActionModule(ActionBase):
|
||||||
|
|
||||||
TRANSFERS_FILES = True
|
TRANSFERS_FILES = True
|
||||||
@@ -62,19 +28,19 @@ class ActionModule(ActionBase):
|
|||||||
def _ensure_invocation(self, result):
|
def _ensure_invocation(self, result):
|
||||||
# NOTE: adding invocation arguments here needs to be kept in sync with
|
# NOTE: adding invocation arguments here needs to be kept in sync with
|
||||||
# any no_log specified in the argument_spec in the module.
|
# any no_log specified in the argument_spec in the module.
|
||||||
if "invocation" not in result:
|
if 'invocation' not in result:
|
||||||
if self._play_context.no_log:
|
if self._play_context.no_log:
|
||||||
result["invocation"] = "CENSORED: no_log is set"
|
result['invocation'] = "CENSORED: no_log is set"
|
||||||
else:
|
else:
|
||||||
result["invocation"] = self._task.args.copy()
|
result['invocation'] = self._task.args.copy()
|
||||||
result["invocation"]["module_args"] = self._task.args.copy()
|
result['invocation']['module_args'] = self._task.args.copy()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def get_template_data(self, template_path):
|
def get_template_data(self, template_path):
|
||||||
try:
|
try:
|
||||||
source = self._find_needle("templates", template_path)
|
source = self._find_needle('templates', template_path)
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
raise AnsibleActionFail(to_text(e))
|
raise AnsibleActionFail(to_text(e))
|
||||||
|
|
||||||
@@ -82,19 +48,15 @@ class ActionModule(ActionBase):
|
|||||||
try:
|
try:
|
||||||
tmp_source = self._loader.get_real_file(source)
|
tmp_source = self._loader.get_real_file(source)
|
||||||
except AnsibleFileNotFound as e:
|
except AnsibleFileNotFound as e:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("could not find template=%s, %s" % (source, to_text(e)))
|
||||||
"could not find template=%s, %s" % (source, to_text(e))
|
b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict')
|
||||||
)
|
|
||||||
b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(b_tmp_source, "rb") as f:
|
with open(b_tmp_source, 'rb') as f:
|
||||||
try:
|
try:
|
||||||
template_data = to_text(f.read(), errors="surrogate_or_strict")
|
template_data = to_text(f.read(), errors='surrogate_or_strict')
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("Template source files must be utf-8 encoded")
|
||||||
"Template source files must be utf-8 encoded"
|
|
||||||
)
|
|
||||||
yield template_data
|
yield template_data
|
||||||
except AnsibleAction:
|
except AnsibleAction:
|
||||||
raise
|
raise
|
||||||
@@ -111,104 +73,63 @@ class ActionModule(ActionBase):
|
|||||||
"block_start_string": None,
|
"block_start_string": None,
|
||||||
"block_end_string": None,
|
"block_end_string": None,
|
||||||
"trim_blocks": True,
|
"trim_blocks": True,
|
||||||
"lstrip_blocks": False,
|
"lstrip_blocks": False
|
||||||
}
|
}
|
||||||
if isinstance(template, string_types):
|
if isinstance(template, string_types):
|
||||||
# treat this as raw_params
|
# treat this as raw_params
|
||||||
template_param["path"] = template
|
template_param['path'] = template
|
||||||
elif isinstance(template, dict):
|
elif isinstance(template, dict):
|
||||||
template_args = template
|
template_args = template
|
||||||
template_path = template_args.get("path", None)
|
template_path = template_args.get('path', None)
|
||||||
if not template_path:
|
if not template_path:
|
||||||
raise AnsibleActionFail("Please specify path for template.")
|
raise AnsibleActionFail("Please specify path for template.")
|
||||||
template_param["path"] = template_path
|
template_param['path'] = template_path
|
||||||
|
|
||||||
# Options type validation strings
|
# Options type validation strings
|
||||||
for s_type in (
|
for s_type in ('newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
|
||||||
"newline_sequence",
|
'block_end_string'):
|
||||||
"variable_start_string",
|
|
||||||
"variable_end_string",
|
|
||||||
"block_start_string",
|
|
||||||
"block_end_string",
|
|
||||||
):
|
|
||||||
if s_type in template_args:
|
if s_type in template_args:
|
||||||
value = ensure_type(template_args[s_type], "string")
|
value = ensure_type(template_args[s_type], 'string')
|
||||||
if value is not None and not isinstance(value, string_types):
|
if value is not None and not isinstance(value, string_types):
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
|
||||||
"%s is expected to be a string, but got %s instead"
|
|
||||||
% (s_type, type(value))
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
template_param.update(
|
template_param.update({
|
||||||
{
|
"trim_blocks": boolean(template_args.get('trim_blocks', True), strict=False),
|
||||||
"trim_blocks": boolean(
|
"lstrip_blocks": boolean(template_args.get('lstrip_blocks', False), strict=False)
|
||||||
template_args.get("trim_blocks", True), strict=False
|
})
|
||||||
),
|
|
||||||
"lstrip_blocks": boolean(
|
|
||||||
template_args.get("lstrip_blocks", False), strict=False
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise AnsibleActionFail(to_native(e))
|
raise AnsibleActionFail(to_native(e))
|
||||||
|
|
||||||
template_param.update(
|
template_param.update({
|
||||||
{
|
"newline_sequence": template_args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE),
|
||||||
"newline_sequence": template_args.get(
|
"variable_start_string": template_args.get('variable_start_string', None),
|
||||||
"newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
|
"variable_end_string": template_args.get('variable_end_string', None),
|
||||||
),
|
"block_start_string": template_args.get('block_start_string', None),
|
||||||
"variable_start_string": template_args.get(
|
"block_end_string": template_args.get('block_end_string', None)
|
||||||
"variable_start_string", None
|
})
|
||||||
),
|
|
||||||
"variable_end_string": template_args.get(
|
|
||||||
"variable_end_string", None
|
|
||||||
),
|
|
||||||
"block_start_string": template_args.get("block_start_string", None),
|
|
||||||
"block_end_string": template_args.get("block_end_string", None),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("Error while reading template file - "
|
||||||
"Error while reading template file - "
|
"a string or dict for template expected, but got %s instead" % type(template))
|
||||||
"a string or dict for template expected, but got %s instead"
|
|
||||||
% type(template)
|
|
||||||
)
|
|
||||||
return template_param
|
return template_param
|
||||||
|
|
||||||
def import_jinja2_lstrip(self, templates):
|
def import_jinja2_lstrip(self, templates):
|
||||||
# Option `lstrip_blocks' was added in Jinja2 version 2.7.
|
# Option `lstrip_blocks' was added in Jinja2 version 2.7.
|
||||||
if any(tmp["lstrip_blocks"] for tmp in templates):
|
if any(tmp['lstrip_blocks'] for tmp in templates):
|
||||||
try:
|
try:
|
||||||
import jinja2.defaults
|
import jinja2.defaults
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise AnsibleError(
|
raise AnsibleError('Unable to import Jinja2 defaults for determining Jinja2 features.')
|
||||||
"Unable to import Jinja2 defaults for determining Jinja2 features."
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
jinja2.defaults.LSTRIP_BLOCKS
|
jinja2.defaults.LSTRIP_BLOCKS
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise AnsibleError(
|
raise AnsibleError("Option `lstrip_blocks' is only available in Jinja2 versions >=2.7")
|
||||||
"Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_template(self, template, new_module_args, task_vars):
|
def load_template(self, template, new_module_args, task_vars):
|
||||||
# template is only supported by k8s module.
|
# template is only supported by k8s module.
|
||||||
if self._task.action not in (
|
if self._task.action not in ('k8s', 'kubernetes.core.k8s', 'community.okd.k8s', 'redhat.openshift.k8s', 'community.kubernetes.k8s'):
|
||||||
"k8s",
|
raise AnsibleActionFail("'template' is only a supported parameter for the 'k8s' module.")
|
||||||
"kubernetes.core.k8s",
|
|
||||||
"community.okd.k8s",
|
|
||||||
"redhat.openshift.k8s",
|
|
||||||
"community.kubernetes.k8s",
|
|
||||||
"openshift_adm_groups_sync",
|
|
||||||
"community.okd.openshift_adm_groups_sync",
|
|
||||||
"redhat.openshift.openshift_adm_groups_sync",
|
|
||||||
):
|
|
||||||
raise AnsibleActionFail(
|
|
||||||
"'template' is only a supported parameter for the 'k8s' module."
|
|
||||||
)
|
|
||||||
|
|
||||||
omit_value = task_vars.get("omit")
|
|
||||||
template_params = []
|
template_params = []
|
||||||
if isinstance(template, string_types) or isinstance(template, dict):
|
if isinstance(template, string_types) or isinstance(template, dict):
|
||||||
template_params.append(self.get_template_args(template))
|
template_params.append(self.get_template_args(template))
|
||||||
@@ -216,11 +137,8 @@ class ActionModule(ActionBase):
|
|||||||
for element in template:
|
for element in template:
|
||||||
template_params.append(self.get_template_args(element))
|
template_params.append(self.get_template_args(element))
|
||||||
else:
|
else:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("Error while reading template file - "
|
||||||
"Error while reading template file - "
|
"a string or dict for template expected, but got %s instead" % type(template))
|
||||||
"a string or dict for template expected, but got %s instead"
|
|
||||||
% type(template)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.import_jinja2_lstrip(template_params)
|
self.import_jinja2_lstrip(template_params)
|
||||||
|
|
||||||
@@ -231,31 +149,20 @@ class ActionModule(ActionBase):
|
|||||||
old_vars = self._templar.available_variables
|
old_vars = self._templar.available_variables
|
||||||
|
|
||||||
default_environment = {}
|
default_environment = {}
|
||||||
for key in (
|
for key in ("newline_sequence", "variable_start_string", "variable_end_string",
|
||||||
"newline_sequence",
|
"block_start_string", "block_end_string", "trim_blocks", "lstrip_blocks"):
|
||||||
"variable_start_string",
|
|
||||||
"variable_end_string",
|
|
||||||
"block_start_string",
|
|
||||||
"block_end_string",
|
|
||||||
"trim_blocks",
|
|
||||||
"lstrip_blocks",
|
|
||||||
):
|
|
||||||
if hasattr(self._templar.environment, key):
|
if hasattr(self._templar.environment, key):
|
||||||
default_environment[key] = getattr(self._templar.environment, key)
|
default_environment[key] = getattr(self._templar.environment, key)
|
||||||
for template_item in template_params:
|
for template_item in template_params:
|
||||||
# We need to convert unescaped sequences to proper escaped sequences for Jinja2
|
# We need to convert unescaped sequences to proper escaped sequences for Jinja2
|
||||||
newline_sequence = template_item["newline_sequence"]
|
newline_sequence = template_item['newline_sequence']
|
||||||
if newline_sequence in wrong_sequences:
|
if newline_sequence in wrong_sequences:
|
||||||
template_item["newline_sequence"] = allowed_sequences[
|
template_item['newline_sequence'] = allowed_sequences[wrong_sequences.index(newline_sequence)]
|
||||||
wrong_sequences.index(newline_sequence)
|
|
||||||
]
|
|
||||||
elif newline_sequence not in allowed_sequences:
|
elif newline_sequence not in allowed_sequences:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")
|
||||||
"newline_sequence needs to be one of: \n, \r or \r\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# template the source data locally & get ready to transfer
|
# template the source data locally & get ready to transfer
|
||||||
with self.get_template_data(template_item["path"]) as template_data:
|
with self.get_template_data(template_item['path']) as template_data:
|
||||||
# add ansible 'template' vars
|
# add ansible 'template' vars
|
||||||
temp_vars = copy.deepcopy(task_vars)
|
temp_vars = copy.deepcopy(task_vars)
|
||||||
for key, value in iteritems(template_item):
|
for key, value in iteritems(template_item):
|
||||||
@@ -263,48 +170,29 @@ class ActionModule(ActionBase):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
setattr(self._templar.environment, key, value)
|
setattr(self._templar.environment, key, value)
|
||||||
else:
|
else:
|
||||||
setattr(
|
setattr(self._templar.environment, key, default_environment.get(key))
|
||||||
self._templar.environment,
|
|
||||||
key,
|
|
||||||
default_environment.get(key),
|
|
||||||
)
|
|
||||||
self._templar.available_variables = temp_vars
|
self._templar.available_variables = temp_vars
|
||||||
result = self._templar.do_template(
|
result = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
|
||||||
template_data,
|
result_template.append(result)
|
||||||
preserve_trailing_newlines=True,
|
|
||||||
escape_backslashes=False,
|
|
||||||
)
|
|
||||||
if omit_value is not None:
|
|
||||||
result_template.extend(RemoveOmit(result, omit_value).output())
|
|
||||||
else:
|
|
||||||
result_template.append(result)
|
|
||||||
self._templar.available_variables = old_vars
|
self._templar.available_variables = old_vars
|
||||||
resource_definition = self._task.args.get("definition", None)
|
resource_definition = self._task.args.get('definition', None)
|
||||||
if not resource_definition:
|
if not resource_definition:
|
||||||
new_module_args.pop("template")
|
new_module_args.pop('template')
|
||||||
new_module_args["definition"] = result_template
|
new_module_args['definition'] = result_template
|
||||||
|
|
||||||
def get_file_realpath(self, local_path):
|
def get_file_realpath(self, local_path):
|
||||||
# local_path is only supported by k8s_cp module.
|
# local_path is only supported by k8s_cp module.
|
||||||
if self._task.action not in (
|
if self._task.action not in ('k8s_cp', 'kubernetes.core.k8s_cp', 'community.kubernetes.k8s_cp'):
|
||||||
"k8s_cp",
|
raise AnsibleActionFail("'local_path' is only supported parameter for 'k8s_cp' module.")
|
||||||
"kubernetes.core.k8s_cp",
|
|
||||||
"community.kubernetes.k8s_cp",
|
|
||||||
):
|
|
||||||
raise AnsibleActionFail(
|
|
||||||
"'local_path' is only supported parameter for 'k8s_cp' module."
|
|
||||||
)
|
|
||||||
|
|
||||||
if os.path.exists(local_path):
|
if os.path.exists(local_path):
|
||||||
return local_path
|
return local_path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# find in expected paths
|
# find in expected paths
|
||||||
return self._find_needle("files", local_path)
|
return self._find_needle('files', local_path)
|
||||||
except AnsibleError:
|
except AnsibleError:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("%s does not exist in local filesystem" % local_path)
|
||||||
"%s does not exist in local filesystem" % local_path
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
|
def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
|
||||||
if isinstance(kubeconfig, string_types):
|
if isinstance(kubeconfig, string_types):
|
||||||
@@ -312,26 +200,20 @@ class ActionModule(ActionBase):
|
|||||||
if not remote_transport:
|
if not remote_transport:
|
||||||
# kubeconfig is local
|
# kubeconfig is local
|
||||||
# find in expected paths
|
# find in expected paths
|
||||||
configs = []
|
kubeconfig = self._find_needle('files', kubeconfig)
|
||||||
for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
|
|
||||||
config = self._find_needle("files", config)
|
|
||||||
|
|
||||||
# decrypt kubeconfig found
|
# decrypt kubeconfig found
|
||||||
configs.append(self._loader.get_real_file(config, decrypt=True))
|
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
||||||
new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
|
new_module_args['kubeconfig'] = actual_file
|
||||||
configs
|
|
||||||
)
|
|
||||||
|
|
||||||
elif isinstance(kubeconfig, dict):
|
elif isinstance(kubeconfig, dict):
|
||||||
new_module_args["kubeconfig"] = kubeconfig
|
new_module_args['kubeconfig'] = kubeconfig
|
||||||
else:
|
else:
|
||||||
raise AnsibleActionFail(
|
raise AnsibleActionFail("Error while reading kubeconfig parameter - "
|
||||||
"Error while reading kubeconfig parameter - "
|
"a string or dict expected, but got %s instead" % type(kubeconfig))
|
||||||
"a string or dict expected, but got %s instead" % type(kubeconfig)
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
def run(self, tmp=None, task_vars=None):
|
||||||
"""handler for k8s options"""
|
''' handler for k8s options '''
|
||||||
if task_vars is None:
|
if task_vars is None:
|
||||||
task_vars = dict()
|
task_vars = dict()
|
||||||
|
|
||||||
@@ -342,61 +224,53 @@ class ActionModule(ActionBase):
|
|||||||
# look for kubeconfig and src
|
# look for kubeconfig and src
|
||||||
# 'local' => look files on Ansible Controller
|
# 'local' => look files on Ansible Controller
|
||||||
# Transport other than 'local' => look files on remote node
|
# Transport other than 'local' => look files on remote node
|
||||||
remote_transport = self._connection.transport != "local"
|
remote_transport = self._connection.transport != 'local'
|
||||||
|
|
||||||
new_module_args = copy.deepcopy(self._task.args)
|
new_module_args = copy.deepcopy(self._task.args)
|
||||||
|
|
||||||
kubeconfig = self._task.args.get("kubeconfig", None)
|
kubeconfig = self._task.args.get('kubeconfig', None)
|
||||||
if kubeconfig:
|
if kubeconfig:
|
||||||
try:
|
try:
|
||||||
self.get_kubeconfig(kubeconfig, remote_transport, new_module_args)
|
self.get_kubeconfig(kubeconfig, remote_transport, new_module_args)
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
result["failed"] = True
|
result['failed'] = True
|
||||||
result["msg"] = to_text(e)
|
result['msg'] = to_text(e)
|
||||||
result["exception"] = traceback.format_exc()
|
result['exception'] = traceback.format_exc()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# find the file in the expected search path
|
# find the file in the expected search path
|
||||||
src = self._task.args.get("src", None)
|
src = self._task.args.get('src', None)
|
||||||
|
|
||||||
if src and not src.startswith(("http://", "https://", "ftp://")):
|
if src:
|
||||||
if remote_transport:
|
if remote_transport:
|
||||||
# src is on remote node
|
# src is on remote node
|
||||||
result.update(
|
result.update(self._execute_module(module_name=self._task.action, task_vars=task_vars))
|
||||||
self._execute_module(
|
|
||||||
module_name=self._task.action, task_vars=task_vars
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self._ensure_invocation(result)
|
return self._ensure_invocation(result)
|
||||||
|
|
||||||
# src is local
|
# src is local
|
||||||
try:
|
try:
|
||||||
# find in expected paths
|
# find in expected paths
|
||||||
src = self._find_needle("files", src)
|
src = self._find_needle('files', src)
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
result["failed"] = True
|
result['failed'] = True
|
||||||
result["msg"] = to_text(e)
|
result['msg'] = to_text(e)
|
||||||
result["exception"] = traceback.format_exc()
|
result['exception'] = traceback.format_exc()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if src:
|
if src:
|
||||||
new_module_args["src"] = src
|
new_module_args['src'] = src
|
||||||
|
|
||||||
template = self._task.args.get("template", None)
|
template = self._task.args.get('template', None)
|
||||||
if template:
|
if template:
|
||||||
self.load_template(template, new_module_args, task_vars)
|
self.load_template(template, new_module_args, task_vars)
|
||||||
|
|
||||||
local_path = self._task.args.get("local_path")
|
local_path = self._task.args.get('local_path')
|
||||||
state = self._task.args.get("state", None)
|
state = self._task.args.get('state', None)
|
||||||
if local_path and state == "to_pod" and not remote_transport:
|
if local_path and state == 'to_pod':
|
||||||
new_module_args["local_path"] = self.get_file_realpath(local_path)
|
new_module_args['local_path'] = self.get_file_realpath(local_path)
|
||||||
|
|
||||||
# Execute the k8s_* module.
|
# Execute the k8s_* module.
|
||||||
module_return = self._execute_module(
|
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
|
||||||
module_name=self._task.action,
|
|
||||||
module_args=new_module_args,
|
|
||||||
task_vars=task_vars,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete tmp path
|
# Delete tmp path
|
||||||
self._remove_tmp_path(self._connection._shell.tmpdir)
|
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r"""
|
||||||
@@ -75,7 +74,6 @@ DOCUMENTATION = r"""
|
|||||||
kubectl_kubeconfig:
|
kubectl_kubeconfig:
|
||||||
description:
|
description:
|
||||||
- Path to a kubectl config file. Defaults to I(~/.kube/config)
|
- Path to a kubectl config file. Defaults to I(~/.kube/config)
|
||||||
- The configuration can be provided as dictionary. Added in version 2.4.0.
|
|
||||||
default: ''
|
default: ''
|
||||||
vars:
|
vars:
|
||||||
- name: ansible_kubectl_kubeconfig
|
- name: ansible_kubectl_kubeconfig
|
||||||
@@ -176,8 +174,6 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
import json
|
|
||||||
|
|
||||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
||||||
@@ -189,26 +185,26 @@ from ansible.utils.display import Display
|
|||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
CONNECTION_TRANSPORT = "kubectl"
|
CONNECTION_TRANSPORT = 'kubectl'
|
||||||
|
|
||||||
CONNECTION_OPTIONS = {
|
CONNECTION_OPTIONS = {
|
||||||
"kubectl_container": "-c",
|
'kubectl_container': '-c',
|
||||||
"kubectl_namespace": "-n",
|
'kubectl_namespace': '-n',
|
||||||
"kubectl_kubeconfig": "--kubeconfig",
|
'kubectl_kubeconfig': '--kubeconfig',
|
||||||
"kubectl_context": "--context",
|
'kubectl_context': '--context',
|
||||||
"kubectl_host": "--server",
|
'kubectl_host': '--server',
|
||||||
"kubectl_username": "--username",
|
'kubectl_username': '--username',
|
||||||
"kubectl_password": "--password",
|
'kubectl_password': '--password',
|
||||||
"client_cert": "--client-certificate",
|
'client_cert': '--client-certificate',
|
||||||
"client_key": "--client-key",
|
'client_key': '--client-key',
|
||||||
"ca_cert": "--certificate-authority",
|
'ca_cert': '--certificate-authority',
|
||||||
"validate_certs": "--insecure-skip-tls-verify",
|
'validate_certs': '--insecure-skip-tls-verify',
|
||||||
"kubectl_token": "--token",
|
'kubectl_token': '--token'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Connection(ConnectionBase):
|
class Connection(ConnectionBase):
|
||||||
"""Local kubectl based connections"""
|
''' Local kubectl based connections '''
|
||||||
|
|
||||||
transport = CONNECTION_TRANSPORT
|
transport = CONNECTION_TRANSPORT
|
||||||
connection_options = CONNECTION_OPTIONS
|
connection_options = CONNECTION_OPTIONS
|
||||||
@@ -225,220 +221,148 @@ class Connection(ConnectionBase):
|
|||||||
self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
|
self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
|
||||||
if not self.transport_cmd:
|
if not self.transport_cmd:
|
||||||
raise AnsibleError("{0} command not found in PATH".format(self.transport))
|
raise AnsibleError("{0} command not found in PATH".format(self.transport))
|
||||||
self._file_to_delete = None
|
|
||||||
|
|
||||||
def delete_temporary_file(self):
|
|
||||||
if self._file_to_delete is not None:
|
|
||||||
os.remove(self._file_to_delete)
|
|
||||||
self._file_to_delete = None
|
|
||||||
|
|
||||||
def _build_exec_cmd(self, cmd):
|
def _build_exec_cmd(self, cmd):
|
||||||
"""Build the local kubectl exec command to run cmd on remote_host"""
|
""" Build the local kubectl exec command to run cmd on remote_host
|
||||||
|
"""
|
||||||
local_cmd = [self.transport_cmd]
|
local_cmd = [self.transport_cmd]
|
||||||
censored_local_cmd = [self.transport_cmd]
|
censored_local_cmd = [self.transport_cmd]
|
||||||
|
|
||||||
# Build command options based on doc string
|
# Build command options based on doc string
|
||||||
doc_yaml = AnsibleLoader(self.documentation).get_single_data()
|
doc_yaml = AnsibleLoader(self.documentation).get_single_data()
|
||||||
for key in doc_yaml.get("options"):
|
for key in doc_yaml.get('options'):
|
||||||
if key.endswith("verify_ssl") and self.get_option(key) != "":
|
if key.endswith('verify_ssl') and self.get_option(key) != '':
|
||||||
# Translate verify_ssl to skip_verify_ssl, and output as string
|
# Translate verify_ssl to skip_verify_ssl, and output as string
|
||||||
skip_verify_ssl = not self.get_option(key)
|
skip_verify_ssl = not self.get_option(key)
|
||||||
local_cmd.append(
|
local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
||||||
"{0}={1}".format(
|
censored_local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
||||||
self.connection_options[key], str(skip_verify_ssl).lower()
|
elif not key.endswith('container') and self.get_option(key) and self.connection_options.get(key):
|
||||||
)
|
|
||||||
)
|
|
||||||
censored_local_cmd.append(
|
|
||||||
"{0}={1}".format(
|
|
||||||
self.connection_options[key], str(skip_verify_ssl).lower()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif key.endswith("kubeconfig") and self.get_option(key) != "":
|
|
||||||
kubeconfig_path = self.get_option(key)
|
|
||||||
if isinstance(kubeconfig_path, dict):
|
|
||||||
fd, tmpfile = tempfile.mkstemp()
|
|
||||||
with os.fdopen(fd, "w") as fp:
|
|
||||||
json.dump(kubeconfig_path, fp)
|
|
||||||
kubeconfig_path = tmpfile
|
|
||||||
self._file_to_delete = tmpfile
|
|
||||||
|
|
||||||
cmd_arg = self.connection_options[key]
|
|
||||||
local_cmd += [cmd_arg, kubeconfig_path]
|
|
||||||
censored_local_cmd += [cmd_arg, kubeconfig_path]
|
|
||||||
elif (
|
|
||||||
not key.endswith("container")
|
|
||||||
and self.get_option(key)
|
|
||||||
and self.connection_options.get(key)
|
|
||||||
):
|
|
||||||
cmd_arg = self.connection_options[key]
|
cmd_arg = self.connection_options[key]
|
||||||
local_cmd += [cmd_arg, self.get_option(key)]
|
local_cmd += [cmd_arg, self.get_option(key)]
|
||||||
# Redact password and token from console log
|
# Redact password and token from console log
|
||||||
if key.endswith(("_token", "_password")):
|
if key.endswith(('_token', '_password')):
|
||||||
censored_local_cmd += [cmd_arg, "********"]
|
censored_local_cmd += [cmd_arg, '********']
|
||||||
else:
|
else:
|
||||||
censored_local_cmd += [cmd_arg, self.get_option(key)]
|
censored_local_cmd += [cmd_arg, self.get_option(key)]
|
||||||
|
|
||||||
extra_args_name = "{0}_extra_args".format(self.transport)
|
extra_args_name = u'{0}_extra_args'.format(self.transport)
|
||||||
if self.get_option(extra_args_name):
|
if self.get_option(extra_args_name):
|
||||||
local_cmd += self.get_option(extra_args_name).split(" ")
|
local_cmd += self.get_option(extra_args_name).split(' ')
|
||||||
censored_local_cmd += self.get_option(extra_args_name).split(" ")
|
censored_local_cmd += self.get_option(extra_args_name).split(' ')
|
||||||
|
|
||||||
pod = self.get_option("{0}_pod".format(self.transport))
|
pod = self.get_option(u'{0}_pod'.format(self.transport))
|
||||||
if not pod:
|
if not pod:
|
||||||
pod = self._play_context.remote_addr
|
pod = self._play_context.remote_addr
|
||||||
# -i is needed to keep stdin open which allows pipelining to work
|
# -i is needed to keep stdin open which allows pipelining to work
|
||||||
local_cmd += ["exec", "-i", pod]
|
local_cmd += ['exec', '-i', pod]
|
||||||
censored_local_cmd += ["exec", "-i", pod]
|
censored_local_cmd += ['exec', '-i', pod]
|
||||||
|
|
||||||
# if the pod has more than one container, then container is required
|
# if the pod has more than one container, then container is required
|
||||||
container_arg_name = "{0}_container".format(self.transport)
|
container_arg_name = u'{0}_container'.format(self.transport)
|
||||||
if self.get_option(container_arg_name):
|
if self.get_option(container_arg_name):
|
||||||
local_cmd += ["-c", self.get_option(container_arg_name)]
|
local_cmd += ['-c', self.get_option(container_arg_name)]
|
||||||
censored_local_cmd += ["-c", self.get_option(container_arg_name)]
|
censored_local_cmd += ['-c', self.get_option(container_arg_name)]
|
||||||
|
|
||||||
local_cmd += ["--"] + cmd
|
local_cmd += ['--'] + cmd
|
||||||
censored_local_cmd += ["--"] + cmd
|
censored_local_cmd += ['--'] + cmd
|
||||||
|
|
||||||
return local_cmd, censored_local_cmd
|
return local_cmd, censored_local_cmd
|
||||||
|
|
||||||
def _connect(self, port=None):
|
def _connect(self, port=None):
|
||||||
"""Connect to the container. Nothing to do"""
|
""" Connect to the container. Nothing to do """
|
||||||
super(Connection, self)._connect()
|
super(Connection, self)._connect()
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
display.vvv(
|
display.vvv(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._play_context.remote_addr)
|
||||||
"ESTABLISH {0} CONNECTION".format(self.transport),
|
|
||||||
host=self._play_context.remote_addr,
|
|
||||||
)
|
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||||
"""Run a command in the container"""
|
""" Run a command in the container """
|
||||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||||
|
|
||||||
local_cmd, censored_local_cmd = self._build_exec_cmd(
|
local_cmd, censored_local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
|
||||||
[self._play_context.executable, "-c", cmd]
|
|
||||||
)
|
|
||||||
|
|
||||||
display.vvv(
|
display.vvv("EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr)
|
||||||
"EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr
|
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||||
)
|
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
|
||||||
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
p = subprocess.Popen(
|
|
||||||
local_cmd,
|
|
||||||
shell=False,
|
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
|
|
||||||
stdout, stderr = p.communicate(in_data)
|
stdout, stderr = p.communicate(in_data)
|
||||||
self.delete_temporary_file()
|
|
||||||
return (p.returncode, stdout, stderr)
|
return (p.returncode, stdout, stderr)
|
||||||
|
|
||||||
def _prefix_login_path(self, remote_path):
|
def _prefix_login_path(self, remote_path):
|
||||||
"""Make sure that we put files into a standard path
|
''' Make sure that we put files into a standard path
|
||||||
|
|
||||||
If a path is relative, then we need to choose where to put it.
|
If a path is relative, then we need to choose where to put it.
|
||||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||||
exist in any given chroot. So for now we're choosing "/" instead.
|
exist in any given chroot. So for now we're choosing "/" instead.
|
||||||
This also happens to be the former default.
|
This also happens to be the former default.
|
||||||
|
|
||||||
Can revisit using $HOME instead if it's a problem
|
Can revisit using $HOME instead if it's a problem
|
||||||
"""
|
'''
|
||||||
if not remote_path.startswith(os.path.sep):
|
if not remote_path.startswith(os.path.sep):
|
||||||
remote_path = os.path.join(os.path.sep, remote_path)
|
remote_path = os.path.join(os.path.sep, remote_path)
|
||||||
return os.path.normpath(remote_path)
|
return os.path.normpath(remote_path)
|
||||||
|
|
||||||
def put_file(self, in_path, out_path):
|
def put_file(self, in_path, out_path):
|
||||||
"""Transfer a file from local to the container"""
|
""" Transfer a file from local to the container """
|
||||||
super(Connection, self).put_file(in_path, out_path)
|
super(Connection, self).put_file(in_path, out_path)
|
||||||
display.vvv(
|
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
||||||
"PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
|
|
||||||
)
|
|
||||||
|
|
||||||
out_path = self._prefix_login_path(out_path)
|
out_path = self._prefix_login_path(out_path)
|
||||||
if not os.path.exists(to_bytes(in_path, errors="surrogate_or_strict")):
|
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||||
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
|
raise AnsibleFileNotFound(
|
||||||
|
"file or module does not exist: %s" % in_path)
|
||||||
|
|
||||||
out_path = shlex_quote(out_path)
|
out_path = shlex_quote(out_path)
|
||||||
# kubectl doesn't have native support for copying files into
|
# kubectl doesn't have native support for copying files into
|
||||||
# running containers, so we use kubectl exec to implement this
|
# running containers, so we use kubectl exec to implement this
|
||||||
with open(to_bytes(in_path, errors="surrogate_or_strict"), "rb") as in_file:
|
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
|
||||||
if not os.fstat(in_file.fileno()).st_size:
|
if not os.fstat(in_file.fileno()).st_size:
|
||||||
count = " count=0"
|
count = ' count=0'
|
||||||
else:
|
else:
|
||||||
count = ""
|
count = ''
|
||||||
args, dummy = self._build_exec_cmd(
|
args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
|
||||||
[
|
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||||
self._play_context.executable,
|
|
||||||
"-c",
|
|
||||||
"dd of=%s bs=%s%s && sleep 0" % (out_path, BUFSIZE, count),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
|
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(args, stdin=in_file,
|
||||||
args, stdin=in_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
raise AnsibleError(
|
raise AnsibleError("kubectl connection requires dd command in the container to put files")
|
||||||
"kubectl connection requires dd command in the container to put files"
|
|
||||||
)
|
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
self.delete_temporary_file()
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise AnsibleError(
|
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||||
"failed to transfer file %s to %s:\n%s\n%s"
|
|
||||||
% (in_path, out_path, stdout, stderr)
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_file(self, in_path, out_path):
|
def fetch_file(self, in_path, out_path):
|
||||||
"""Fetch a file from container to local."""
|
""" Fetch a file from container to local. """
|
||||||
super(Connection, self).fetch_file(in_path, out_path)
|
super(Connection, self).fetch_file(in_path, out_path)
|
||||||
display.vvv(
|
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
||||||
"FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
|
|
||||||
)
|
|
||||||
|
|
||||||
in_path = self._prefix_login_path(in_path)
|
in_path = self._prefix_login_path(in_path)
|
||||||
out_dir = os.path.dirname(out_path)
|
out_dir = os.path.dirname(out_path)
|
||||||
|
|
||||||
# kubectl doesn't have native support for fetching files from
|
# kubectl doesn't have native support for fetching files from
|
||||||
# running containers, so we use kubectl exec to implement this
|
# running containers, so we use kubectl exec to implement this
|
||||||
args, dummy = self._build_exec_cmd(
|
args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
|
||||||
[self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)]
|
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||||
)
|
|
||||||
args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
|
|
||||||
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
|
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
|
||||||
with open(
|
with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
|
||||||
to_bytes(actual_out_path, errors="surrogate_or_strict"), "wb"
|
|
||||||
) as out_file:
|
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||||
args, stdin=subprocess.PIPE, stdout=out_file, stderr=subprocess.PIPE
|
stdout=out_file, stderr=subprocess.PIPE)
|
||||||
)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
raise AnsibleError(
|
raise AnsibleError(
|
||||||
"{0} connection requires dd command in the container to fetch files".format(
|
"{0} connection requires dd command in the container to fetch files".format(self.transport)
|
||||||
self.transport
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
self.delete_temporary_file()
|
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise AnsibleError(
|
raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||||
"failed to fetch file %s to %s:\n%s\n%s"
|
|
||||||
% (in_path, out_path, stdout, stderr)
|
|
||||||
)
|
|
||||||
|
|
||||||
if actual_out_path != out_path:
|
if actual_out_path != out_path:
|
||||||
os.rename(
|
os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
||||||
to_bytes(actual_out_path, errors="strict"),
|
|
||||||
to_bytes(out_path, errors="strict"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Terminate the connection. Nothing to do for kubectl"""
|
""" Terminate the connection. Nothing to do for kubectl"""
|
||||||
super(Connection, self).close()
|
super(Connection, self).close()
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
|
|
||||||
# Options for common Helm modules
|
# Options for common Helm modules
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
binary_path:
|
binary_path:
|
||||||
description:
|
description:
|
||||||
@@ -30,8 +29,7 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Helm option to specify kubeconfig path to use.
|
- Helm option to specify kubeconfig path to use.
|
||||||
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
|
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
|
||||||
- The configuration can be provided as dictionary. Added in version 2.4.0.
|
type: path
|
||||||
type: raw
|
|
||||||
aliases: [ kubeconfig_path ]
|
aliases: [ kubeconfig_path ]
|
||||||
host:
|
host:
|
||||||
description:
|
description:
|
||||||
@@ -58,4 +56,4 @@ options:
|
|||||||
type: path
|
type: path
|
||||||
aliases: [ ssl_ca_cert ]
|
aliases: [ ssl_ca_cert ]
|
||||||
version_added: "1.2.0"
|
version_added: "1.2.0"
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for authenticating with the API.
|
# Options for authenticating with the API.
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
host:
|
host:
|
||||||
description:
|
description:
|
||||||
@@ -28,7 +27,6 @@ options:
|
|||||||
options are provided, the Kubernetes client will attempt to load the default
|
options are provided, the Kubernetes client will attempt to load the default
|
||||||
configuration file from I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment
|
configuration file from I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment
|
||||||
variable.
|
variable.
|
||||||
- Multiple Kubernetes config file can be provided using separator ';' for Windows platform or ':' for others platforms.
|
|
||||||
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.
|
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.
|
||||||
type: raw
|
type: raw
|
||||||
context:
|
context:
|
||||||
@@ -78,14 +76,6 @@ options:
|
|||||||
- The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.
|
- The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.
|
||||||
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
|
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
|
||||||
type: str
|
type: str
|
||||||
no_proxy:
|
|
||||||
description:
|
|
||||||
- The comma separated list of hosts/domains/IP/CIDR that shouldn't go through proxy. Can also be specified via K8S_AUTH_NO_PROXY environment variable.
|
|
||||||
- Please note that this module does not pick up typical proxy settings from the environment (e.g. NO_PROXY).
|
|
||||||
- This feature requires kubernetes>=19.15.0. When kubernetes library is less than 19.15.0, it fails even no_proxy set in correct.
|
|
||||||
- example value is "localhost,.local,.example.com,127.0.0.1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
|
||||||
type: str
|
|
||||||
version_added: 2.3.0
|
|
||||||
proxy_headers:
|
proxy_headers:
|
||||||
description:
|
description:
|
||||||
- The Header used for the HTTP proxy.
|
- The Header used for the HTTP proxy.
|
||||||
@@ -120,21 +110,8 @@ options:
|
|||||||
- Please note that the current version of the k8s python client library does not support setting this flag to True yet.
|
- Please note that the current version of the k8s python client library does not support setting this flag to True yet.
|
||||||
- "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169"
|
- "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169"
|
||||||
type: bool
|
type: bool
|
||||||
impersonate_user:
|
|
||||||
description:
|
|
||||||
- Username to impersonate for the operation.
|
|
||||||
- Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.
|
|
||||||
type: str
|
|
||||||
version_added: 2.3.0
|
|
||||||
impersonate_groups:
|
|
||||||
description:
|
|
||||||
- Group(s) to impersonate for the operation.
|
|
||||||
- "Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2"
|
|
||||||
type: list
|
|
||||||
elements: str
|
|
||||||
version_added: 2.3.0
|
|
||||||
notes:
|
notes:
|
||||||
- "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full
|
- "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full
|
||||||
certificate chain for the API server must be provided via C(ca_cert) or in the
|
certificate chain for the API server must be provided via C(ca_cert) or in the
|
||||||
kubeconfig file."
|
kubeconfig file."
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for specifying object wait
|
# Options for specifying object wait
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
delete_options:
|
delete_options:
|
||||||
type: dict
|
type: dict
|
||||||
@@ -49,4 +48,4 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
- Specify the UID of the target object.
|
- Specify the UID of the target object.
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for selecting or identifying a specific K8s object
|
# Options for selecting or identifying a specific K8s object
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
api_version:
|
api_version:
|
||||||
description:
|
description:
|
||||||
@@ -50,4 +49,4 @@ options:
|
|||||||
- If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
|
- If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
|
||||||
will override this option.
|
will override this option.
|
||||||
type: str
|
type: str
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for providing an object configuration
|
# Options for providing an object configuration
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
resource_definition:
|
resource_definition:
|
||||||
description:
|
description:
|
||||||
@@ -29,7 +28,6 @@ options:
|
|||||||
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
|
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
|
||||||
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
|
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
|
||||||
I(resource_definition). See Examples below.
|
I(resource_definition). See Examples below.
|
||||||
- The URL to manifest files that can be used to create the resource. Added in version 2.4.0.
|
|
||||||
- Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module.
|
- Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module.
|
||||||
type: path
|
type: path
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options used by scale modules.
|
# Options used by scale modules.
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
replicas:
|
replicas:
|
||||||
description:
|
description:
|
||||||
@@ -47,4 +46,4 @@ options:
|
|||||||
default: 5
|
default: 5
|
||||||
type: int
|
type: int
|
||||||
version_added: 2.0.0
|
version_added: 2.0.0
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for specifying object state
|
# Options for specifying object state
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
@@ -28,4 +27,4 @@ options:
|
|||||||
- If set to C(yes), and I(state) is C(present), an existing object will be replaced.
|
- If set to C(yes), and I(state) is C(present), an existing object will be replaced.
|
||||||
type: bool
|
type: bool
|
||||||
default: no
|
default: no
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
|
|
||||||
# Options for specifying object wait
|
# Options for specifying object wait
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
options:
|
options:
|
||||||
wait:
|
wait:
|
||||||
description:
|
description:
|
||||||
@@ -65,4 +64,4 @@ options:
|
|||||||
- The possible reasons in a condition are specific to each resource type in Kubernetes.
|
- The possible reasons in a condition are specific to each resource type in Kubernetes.
|
||||||
- See the API documentation of the status field for a given resource to see possible choices.
|
- See the API documentation of the status field for a given resource to see possible choices.
|
||||||
type: dict
|
type: dict
|
||||||
"""
|
'''
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleFilterError
|
from ansible.errors import AnsibleFilterError
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import generate_hash
|
||||||
generate_hash,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def k8s_config_resource_name(resource):
|
def k8s_config_resource_name(resource):
|
||||||
@@ -18,14 +15,15 @@ def k8s_config_resource_name(resource):
|
|||||||
Generate resource name for the given resource of type ConfigMap, Secret
|
Generate resource name for the given resource of type ConfigMap, Secret
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return resource["metadata"]["name"] + "-" + generate_hash(resource)
|
return resource['metadata']['name'] + '-' + generate_hash(resource)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AnsibleFilterError(
|
raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name")
|
||||||
"resource must have a metadata.name key to generate a resource name"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---- Ansible filters ----
|
# ---- Ansible filters ----
|
||||||
class FilterModule(object):
|
class FilterModule(object):
|
||||||
|
|
||||||
def filters(self):
|
def filters(self):
|
||||||
return {"k8s_config_resource_name": k8s_config_resource_name}
|
return {
|
||||||
|
'k8s_config_resource_name': k8s_config_resource_name
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
# Copyright (c) Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
DOCUMENTATION:
|
|
||||||
name: k8s_config_resource_name
|
|
||||||
short_description: Generate resource name for the given resource of type ConfigMap, Secret
|
|
||||||
description:
|
|
||||||
- Generate resource name for the given resource of type ConfigMap, Secret.
|
|
||||||
- Resource must have a C(metadata.name) key to generate a resource name
|
|
||||||
options:
|
|
||||||
_input:
|
|
||||||
description:
|
|
||||||
- A valid YAML definition for a ConfigMap or a Secret.
|
|
||||||
type: dict
|
|
||||||
required: true
|
|
||||||
author:
|
|
||||||
- ansible cloud team
|
|
||||||
|
|
||||||
EXAMPLES: |
|
|
||||||
# Dump generated name for a configmap into a variable
|
|
||||||
- set_fact:
|
|
||||||
generated_name: '{{ definition | kubernetes.core.k8s_config_resource_name }}'
|
|
||||||
vars:
|
|
||||||
definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: myconfigmap
|
|
||||||
namespace: mynamespace
|
|
||||||
|
|
||||||
RETURN:
|
|
||||||
_value:
|
|
||||||
description: Generated resource name.
|
|
||||||
type: str
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# Copyright (c) 2018 Ansible Project
|
# Copyright (c) 2018 Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = """
|
DOCUMENTATION = '''
|
||||||
name: k8s
|
name: k8s
|
||||||
author:
|
author:
|
||||||
- Chris Houseknecht (@chouseknecht)
|
- Chris Houseknecht (@chouseknecht)
|
||||||
@@ -89,9 +88,9 @@ DOCUMENTATION = """
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = '''
|
||||||
# File must be named k8s.yaml or k8s.yml
|
# File must be named k8s.yaml or k8s.yml
|
||||||
|
|
||||||
# Authenticate with token, and return all pods and services for all namespaces
|
# Authenticate with token, and return all pods and services for all namespaces
|
||||||
@@ -112,18 +111,12 @@ plugin: kubernetes.core.k8s
|
|||||||
connections:
|
connections:
|
||||||
- kubeconfig: /path/to/config
|
- kubeconfig: /path/to/config
|
||||||
context: 'awx/192-168-64-4:8443/developer'
|
context: 'awx/192-168-64-4:8443/developer'
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception, get_api_client
|
||||||
HAS_K8S_MODULE_HELPER,
|
|
||||||
k8s_import_exception,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -134,24 +127,24 @@ except ImportError:
|
|||||||
|
|
||||||
def format_dynamic_api_exc(exc):
|
def format_dynamic_api_exc(exc):
|
||||||
if exc.body:
|
if exc.body:
|
||||||
if exc.headers and exc.headers.get("Content-Type") == "application/json":
|
if exc.headers and exc.headers.get('Content-Type') == 'application/json':
|
||||||
message = json.loads(exc.body).get("message")
|
message = json.loads(exc.body).get('message')
|
||||||
if message:
|
if message:
|
||||||
return message
|
return message
|
||||||
return exc.body
|
return exc.body
|
||||||
else:
|
else:
|
||||||
return "%s Reason: %s" % (exc.status, exc.reason)
|
return '%s Reason: %s' % (exc.status, exc.reason)
|
||||||
|
|
||||||
|
|
||||||
class K8sInventoryException(Exception):
|
class K8sInventoryException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin):
|
||||||
NAME = "kubernetes.core.k8s"
|
NAME = 'kubernetes.core.k8s'
|
||||||
|
|
||||||
connection_plugin = "kubernetes.core.kubectl"
|
connection_plugin = 'kubernetes.core.kubectl'
|
||||||
transport = "kubectl"
|
transport = 'kubectl'
|
||||||
|
|
||||||
def parse(self, inventory, loader, path, cache=True):
|
def parse(self, inventory, loader, path, cache=True):
|
||||||
super(InventoryModule, self).parse(inventory, loader, path)
|
super(InventoryModule, self).parse(inventory, loader, path)
|
||||||
@@ -160,13 +153,11 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
self.setup(config_data, cache, cache_key)
|
self.setup(config_data, cache, cache_key)
|
||||||
|
|
||||||
def setup(self, config_data, cache, cache_key):
|
def setup(self, config_data, cache, cache_key):
|
||||||
connections = config_data.get("connections")
|
connections = config_data.get('connections')
|
||||||
|
|
||||||
if not HAS_K8S_MODULE_HELPER:
|
if not HAS_K8S_MODULE_HELPER:
|
||||||
raise K8sInventoryException(
|
raise K8sInventoryException(
|
||||||
"This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
|
"This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
|
||||||
k8s_import_exception
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
source_data = None
|
source_data = None
|
||||||
@@ -187,15 +178,11 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
|
|
||||||
for connection in connections:
|
for connection in connections:
|
||||||
if not isinstance(connection, dict):
|
if not isinstance(connection, dict):
|
||||||
raise K8sInventoryException(
|
raise K8sInventoryException("Expecting connection to be a dictionary.")
|
||||||
"Expecting connection to be a dictionary."
|
|
||||||
)
|
|
||||||
client = get_api_client(**connection)
|
client = get_api_client(**connection)
|
||||||
name = connection.get(
|
name = connection.get('name', self.get_default_host_name(client.configuration.host))
|
||||||
"name", self.get_default_host_name(client.configuration.host)
|
if connection.get('namespaces'):
|
||||||
)
|
namespaces = connection['namespaces']
|
||||||
if connection.get("namespaces"):
|
|
||||||
namespaces = connection["namespaces"]
|
|
||||||
else:
|
else:
|
||||||
namespaces = self.get_available_namespaces(client)
|
namespaces = self.get_available_namespaces(client)
|
||||||
for namespace in namespaces:
|
for namespace in namespaces:
|
||||||
@@ -211,36 +198,27 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_host_name(host):
|
def get_default_host_name(host):
|
||||||
return (
|
return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_')
|
||||||
host.replace("https://", "")
|
|
||||||
.replace("http://", "")
|
|
||||||
.replace(".", "-")
|
|
||||||
.replace(":", "_")
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_available_namespaces(self, client):
|
def get_available_namespaces(self, client):
|
||||||
v1_namespace = client.resources.get(api_version="v1", kind="Namespace")
|
v1_namespace = client.resources.get(api_version='v1', kind='Namespace')
|
||||||
try:
|
try:
|
||||||
obj = v1_namespace.get()
|
obj = v1_namespace.get()
|
||||||
except DynamicApiError as exc:
|
except DynamicApiError as exc:
|
||||||
self.display.debug(exc)
|
self.display.debug(exc)
|
||||||
raise K8sInventoryException(
|
raise K8sInventoryException('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc))
|
||||||
"Error fetching Namespace list: %s" % format_dynamic_api_exc(exc)
|
|
||||||
)
|
|
||||||
return [namespace.metadata.name for namespace in obj.items]
|
return [namespace.metadata.name for namespace in obj.items]
|
||||||
|
|
||||||
def get_pods_for_namespace(self, client, name, namespace):
|
def get_pods_for_namespace(self, client, name, namespace):
|
||||||
v1_pod = client.resources.get(api_version="v1", kind="Pod")
|
v1_pod = client.resources.get(api_version='v1', kind='Pod')
|
||||||
try:
|
try:
|
||||||
obj = v1_pod.get(namespace=namespace)
|
obj = v1_pod.get(namespace=namespace)
|
||||||
except DynamicApiError as exc:
|
except DynamicApiError as exc:
|
||||||
self.display.debug(exc)
|
self.display.debug(exc)
|
||||||
raise K8sInventoryException(
|
raise K8sInventoryException('Error fetching Pod list: %s' % format_dynamic_api_exc(exc))
|
||||||
"Error fetching Pod list: %s" % format_dynamic_api_exc(exc)
|
|
||||||
)
|
|
||||||
|
|
||||||
namespace_group = "namespace_{0}".format(namespace)
|
namespace_group = 'namespace_{0}'.format(namespace)
|
||||||
namespace_pods_group = "{0}_pods".format(namespace_group)
|
namespace_pods_group = '{0}_pods'.format(namespace_group)
|
||||||
|
|
||||||
self.inventory.add_group(name)
|
self.inventory.add_group(name)
|
||||||
self.inventory.add_group(namespace_group)
|
self.inventory.add_group(namespace_group)
|
||||||
@@ -251,14 +229,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
for pod in obj.items:
|
for pod in obj.items:
|
||||||
pod_name = pod.metadata.name
|
pod_name = pod.metadata.name
|
||||||
pod_groups = []
|
pod_groups = []
|
||||||
pod_annotations = (
|
pod_annotations = {} if not pod.metadata.annotations else dict(pod.metadata.annotations)
|
||||||
{} if not pod.metadata.annotations else dict(pod.metadata.annotations)
|
|
||||||
)
|
|
||||||
|
|
||||||
if pod.metadata.labels:
|
if pod.metadata.labels:
|
||||||
# create a group for each label_value
|
# create a group for each label_value
|
||||||
for key, value in pod.metadata.labels:
|
for key, value in pod.metadata.labels:
|
||||||
group_name = "label_{0}_{1}".format(key, value)
|
group_name = 'label_{0}_{1}'.format(key, value)
|
||||||
if group_name not in pod_groups:
|
if group_name not in pod_groups:
|
||||||
pod_groups.append(group_name)
|
pod_groups.append(group_name)
|
||||||
self.inventory.add_group(group_name)
|
self.inventory.add_group(group_name)
|
||||||
@@ -271,7 +247,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
|
|
||||||
for container in pod.status.containerStatuses:
|
for container in pod.status.containerStatuses:
|
||||||
# add each pod_container to the namespace group, and to each label_value group
|
# add each pod_container to the namespace group, and to each label_value group
|
||||||
container_name = "{0}_{1}".format(pod.metadata.name, container.name)
|
container_name = '{0}_{1}'.format(pod.metadata.name, container.name)
|
||||||
self.inventory.add_host(container_name)
|
self.inventory.add_host(container_name)
|
||||||
self.inventory.add_child(namespace_pods_group, container_name)
|
self.inventory.add_child(namespace_pods_group, container_name)
|
||||||
if pod_groups:
|
if pod_groups:
|
||||||
@@ -279,85 +255,46 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
self.inventory.add_child(group, container_name)
|
self.inventory.add_child(group, container_name)
|
||||||
|
|
||||||
# Add hostvars
|
# Add hostvars
|
||||||
self.inventory.set_variable(container_name, "object_type", "pod")
|
self.inventory.set_variable(container_name, 'object_type', 'pod')
|
||||||
self.inventory.set_variable(container_name, "labels", pod_labels)
|
self.inventory.set_variable(container_name, 'labels', pod_labels)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'annotations', pod_annotations)
|
||||||
container_name, "annotations", pod_annotations
|
self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName)
|
||||||
)
|
self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'pod_name', pod.spec.name)
|
||||||
container_name, "cluster_name", pod.metadata.clusterName
|
self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP)
|
||||||
)
|
self.inventory.set_variable(container_name, 'pod_phase', pod.status.phase)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'pod_ip', pod.status.podIP)
|
||||||
container_name, "pod_node_name", pod.spec.nodeName
|
self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink)
|
||||||
)
|
self.inventory.set_variable(container_name, 'pod_resource_version', pod.metadata.resourceVersion)
|
||||||
self.inventory.set_variable(container_name, "pod_name", pod.spec.name)
|
self.inventory.set_variable(container_name, 'pod_uid', pod.metadata.uid)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'container_name', container.image)
|
||||||
container_name, "pod_host_ip", pod.status.hostIP
|
self.inventory.set_variable(container_name, 'container_image', container.image)
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "pod_phase", pod.status.phase
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(container_name, "pod_ip", pod.status.podIP)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "pod_self_link", pod.metadata.selfLink
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "pod_resource_version", pod.metadata.resourceVersion
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(container_name, "pod_uid", pod.metadata.uid)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "container_name", container.image
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "container_image", container.image
|
|
||||||
)
|
|
||||||
if container.state.running:
|
if container.state.running:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'container_state', 'Running')
|
||||||
container_name, "container_state", "Running"
|
|
||||||
)
|
|
||||||
if container.state.terminated:
|
if container.state.terminated:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'container_state', 'Terminated')
|
||||||
container_name, "container_state", "Terminated"
|
|
||||||
)
|
|
||||||
if container.state.waiting:
|
if container.state.waiting:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'container_state', 'Waiting')
|
||||||
container_name, "container_state", "Waiting"
|
self.inventory.set_variable(container_name, 'container_ready', container.ready)
|
||||||
)
|
self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/')
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'ansible_connection', self.connection_plugin)
|
||||||
container_name, "container_ready", container.ready
|
self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport),
|
||||||
)
|
pod_name)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport),
|
||||||
container_name, "ansible_remote_tmp", "/tmp/"
|
container.name)
|
||||||
)
|
self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport),
|
||||||
self.inventory.set_variable(
|
namespace)
|
||||||
container_name, "ansible_connection", self.connection_plugin
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name, "ansible_{0}_pod".format(self.transport), pod_name
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name,
|
|
||||||
"ansible_{0}_container".format(self.transport),
|
|
||||||
container.name,
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
container_name,
|
|
||||||
"ansible_{0}_namespace".format(self.transport),
|
|
||||||
namespace,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_services_for_namespace(self, client, name, namespace):
|
def get_services_for_namespace(self, client, name, namespace):
|
||||||
v1_service = client.resources.get(api_version="v1", kind="Service")
|
v1_service = client.resources.get(api_version='v1', kind='Service')
|
||||||
try:
|
try:
|
||||||
obj = v1_service.get(namespace=namespace)
|
obj = v1_service.get(namespace=namespace)
|
||||||
except DynamicApiError as exc:
|
except DynamicApiError as exc:
|
||||||
self.display.debug(exc)
|
self.display.debug(exc)
|
||||||
raise K8sInventoryException(
|
raise K8sInventoryException('Error fetching Service list: %s' % format_dynamic_api_exc(exc))
|
||||||
"Error fetching Service list: %s" % format_dynamic_api_exc(exc)
|
|
||||||
)
|
|
||||||
|
|
||||||
namespace_group = "namespace_{0}".format(namespace)
|
namespace_group = 'namespace_{0}'.format(namespace)
|
||||||
namespace_services_group = "{0}_services".format(namespace_group)
|
namespace_services_group = '{0}_services'.format(namespace_group)
|
||||||
|
|
||||||
self.inventory.add_group(name)
|
self.inventory.add_group(name)
|
||||||
self.inventory.add_group(namespace_group)
|
self.inventory.add_group(namespace_group)
|
||||||
@@ -367,21 +304,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
|
|
||||||
for service in obj.items:
|
for service in obj.items:
|
||||||
service_name = service.metadata.name
|
service_name = service.metadata.name
|
||||||
service_labels = (
|
service_labels = {} if not service.metadata.labels else dict(service.metadata.labels)
|
||||||
{} if not service.metadata.labels else dict(service.metadata.labels)
|
service_annotations = {} if not service.metadata.annotations else dict(service.metadata.annotations)
|
||||||
)
|
|
||||||
service_annotations = (
|
|
||||||
{}
|
|
||||||
if not service.metadata.annotations
|
|
||||||
else dict(service.metadata.annotations)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.inventory.add_host(service_name)
|
self.inventory.add_host(service_name)
|
||||||
|
|
||||||
if service.metadata.labels:
|
if service.metadata.labels:
|
||||||
# create a group for each label_value
|
# create a group for each label_value
|
||||||
for key, value in service.metadata.labels:
|
for key, value in service.metadata.labels:
|
||||||
group_name = "label_{0}_{1}".format(key, value)
|
group_name = 'label_{0}_{1}'.format(key, value)
|
||||||
self.inventory.add_group(group_name)
|
self.inventory.add_group(group_name)
|
||||||
self.inventory.add_child(group_name, service_name)
|
self.inventory.add_child(group_name, service_name)
|
||||||
|
|
||||||
@@ -390,75 +321,42 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
except AnsibleError:
|
except AnsibleError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
ports = [
|
ports = [{'name': port.name,
|
||||||
{
|
'port': port.port,
|
||||||
"name": port.name,
|
'protocol': port.protocol,
|
||||||
"port": port.port,
|
'targetPort': port.targetPort,
|
||||||
"protocol": port.protocol,
|
'nodePort': port.nodePort} for port in service.spec.ports or []]
|
||||||
"targetPort": port.targetPort,
|
|
||||||
"nodePort": port.nodePort,
|
|
||||||
}
|
|
||||||
for port in service.spec.ports or []
|
|
||||||
]
|
|
||||||
|
|
||||||
# add hostvars
|
# add hostvars
|
||||||
self.inventory.set_variable(service_name, "object_type", "service")
|
self.inventory.set_variable(service_name, 'object_type', 'service')
|
||||||
self.inventory.set_variable(service_name, "labels", service_labels)
|
self.inventory.set_variable(service_name, 'labels', service_labels)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'annotations', service_annotations)
|
||||||
service_name, "annotations", service_annotations
|
self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName)
|
||||||
)
|
self.inventory.set_variable(service_name, 'ports', ports)
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'type', service.spec.type)
|
||||||
service_name, "cluster_name", service.metadata.clusterName
|
self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink)
|
||||||
)
|
self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion)
|
||||||
self.inventory.set_variable(service_name, "ports", ports)
|
self.inventory.set_variable(service_name, 'uid', service.metadata.uid)
|
||||||
self.inventory.set_variable(service_name, "type", service.spec.type)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
service_name, "self_link", service.metadata.selfLink
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(
|
|
||||||
service_name, "resource_version", service.metadata.resourceVersion
|
|
||||||
)
|
|
||||||
self.inventory.set_variable(service_name, "uid", service.metadata.uid)
|
|
||||||
|
|
||||||
if service.spec.externalTrafficPolicy:
|
if service.spec.externalTrafficPolicy:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'external_traffic_policy',
|
||||||
service_name,
|
service.spec.externalTrafficPolicy)
|
||||||
"external_traffic_policy",
|
|
||||||
service.spec.externalTrafficPolicy,
|
|
||||||
)
|
|
||||||
if service.spec.externalIPs:
|
if service.spec.externalIPs:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs)
|
||||||
service_name, "external_ips", service.spec.externalIPs
|
|
||||||
)
|
|
||||||
|
|
||||||
if service.spec.externalName:
|
if service.spec.externalName:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'external_name', service.spec.externalName)
|
||||||
service_name, "external_name", service.spec.externalName
|
|
||||||
)
|
|
||||||
|
|
||||||
if service.spec.healthCheckNodePort:
|
if service.spec.healthCheckNodePort:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'health_check_node_port',
|
||||||
service_name,
|
service.spec.healthCheckNodePort)
|
||||||
"health_check_node_port",
|
|
||||||
service.spec.healthCheckNodePort,
|
|
||||||
)
|
|
||||||
if service.spec.loadBalancerIP:
|
if service.spec.loadBalancerIP:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'load_balancer_ip',
|
||||||
service_name, "load_balancer_ip", service.spec.loadBalancerIP
|
service.spec.loadBalancerIP)
|
||||||
)
|
|
||||||
if service.spec.selector:
|
if service.spec.selector:
|
||||||
self.inventory.set_variable(
|
self.inventory.set_variable(service_name, 'selector', dict(service.spec.selector))
|
||||||
service_name, "selector", dict(service.spec.selector)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress:
|
||||||
hasattr(service.status.loadBalancer, "ingress")
|
load_balancer = [{'hostname': ingress.hostname,
|
||||||
and service.status.loadBalancer.ingress
|
'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress]
|
||||||
):
|
self.inventory.set_variable(service_name, 'load_balancer', load_balancer)
|
||||||
load_balancer = [
|
|
||||||
{"hostname": ingress.hostname, "ip": ingress.ip}
|
|
||||||
for ingress in service.status.loadBalancer.ingress
|
|
||||||
]
|
|
||||||
self.inventory.set_variable(
|
|
||||||
service_name, "load_balancer", load_balancer
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
@@ -174,37 +174,15 @@ RETURN = """
|
|||||||
key1: val1
|
key1: val1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError
|
||||||
from ansible.module_utils.common._collections_compat import KeysView
|
from ansible.module_utils.common._collections_compat import KeysView
|
||||||
from ansible.module_utils.common.validation import check_type_bool
|
from ansible.plugins.lookup import LookupBase
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
|
|
||||||
create_definitions,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
|
|
||||||
except TypeError:
|
|
||||||
enable_turbo_mode = False
|
|
||||||
|
|
||||||
if enable_turbo_mode:
|
|
||||||
try:
|
|
||||||
from ansible_collections.cloud.common.plugins.plugin_utils.turbo.lookup import (
|
|
||||||
TurboLookupBase as LookupBase,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
from ansible.plugins.lookup import LookupBase # noqa: F401
|
|
||||||
else:
|
|
||||||
from ansible.plugins.lookup import LookupBase # noqa: F401
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.dynamic.exceptions import NotFoundError
|
from kubernetes.dynamic.exceptions import NotFoundError
|
||||||
|
|
||||||
HAS_K8S_MODULE_HELPER = True
|
HAS_K8S_MODULE_HELPER = True
|
||||||
k8s_import_exception = None
|
k8s_import_exception = None
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
@@ -212,14 +190,13 @@ except ImportError as e:
|
|||||||
k8s_import_exception = e
|
k8s_import_exception = e
|
||||||
|
|
||||||
|
|
||||||
class KubernetesLookup(object):
|
class KubernetesLookup(K8sAnsibleMixin):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
if not HAS_K8S_MODULE_HELPER:
|
if not HAS_K8S_MODULE_HELPER:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
|
"Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
|
||||||
k8s_import_exception
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.kind = None
|
self.kind = None
|
||||||
@@ -240,38 +217,31 @@ class KubernetesLookup(object):
|
|||||||
self.params = kwargs
|
self.params = kwargs
|
||||||
self.client = get_api_client(**kwargs)
|
self.client = get_api_client(**kwargs)
|
||||||
|
|
||||||
cluster_info = kwargs.get("cluster_info")
|
cluster_info = kwargs.get('cluster_info')
|
||||||
if cluster_info == "version":
|
if cluster_info == 'version':
|
||||||
return [self.client.client.version]
|
return [self.client.version]
|
||||||
if cluster_info == "api_groups":
|
if cluster_info == 'api_groups':
|
||||||
if isinstance(self.client.resources.api_groups, KeysView):
|
if isinstance(self.client.resources.api_groups, KeysView):
|
||||||
return [list(self.client.resources.api_groups)]
|
return [list(self.client.resources.api_groups)]
|
||||||
return [self.client.resources.api_groups]
|
return [self.client.resources.api_groups]
|
||||||
|
|
||||||
self.kind = kwargs.get("kind")
|
self.kind = kwargs.get('kind')
|
||||||
self.name = kwargs.get("resource_name")
|
self.name = kwargs.get('resource_name')
|
||||||
self.namespace = kwargs.get("namespace")
|
self.namespace = kwargs.get('namespace')
|
||||||
self.api_version = kwargs.get("api_version", "v1")
|
self.api_version = kwargs.get('api_version', 'v1')
|
||||||
self.label_selector = kwargs.get("label_selector")
|
self.label_selector = kwargs.get('label_selector')
|
||||||
self.field_selector = kwargs.get("field_selector")
|
self.field_selector = kwargs.get('field_selector')
|
||||||
self.include_uninitialized = kwargs.get("include_uninitialized", False)
|
self.include_uninitialized = kwargs.get('include_uninitialized', False)
|
||||||
|
|
||||||
resource_definition = kwargs.get("resource_definition")
|
resource_definition = kwargs.get('resource_definition')
|
||||||
src = kwargs.get("src")
|
src = kwargs.get('src')
|
||||||
if src:
|
if src:
|
||||||
definitions = create_definitions(params=dict(src=src))
|
resource_definition = self.load_resource_definitions(src)[0]
|
||||||
if definitions:
|
|
||||||
self.kind = definitions[0].kind
|
|
||||||
self.name = definitions[0].name
|
|
||||||
self.namespace = definitions[0].namespace
|
|
||||||
self.api_version = definitions[0].api_version or "v1"
|
|
||||||
if resource_definition:
|
if resource_definition:
|
||||||
self.kind = resource_definition.get("kind", self.kind)
|
self.kind = resource_definition.get('kind', self.kind)
|
||||||
self.api_version = resource_definition.get("apiVersion", self.api_version)
|
self.api_version = resource_definition.get('apiVersion', self.api_version)
|
||||||
self.name = resource_definition.get("metadata", {}).get("name", self.name)
|
self.name = resource_definition.get('metadata', {}).get('name', self.name)
|
||||||
self.namespace = resource_definition.get("metadata", {}).get(
|
self.namespace = resource_definition.get('metadata', {}).get('namespace', self.namespace)
|
||||||
"namespace", self.namespace
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.kind:
|
if not self.kind:
|
||||||
raise AnsibleError(
|
raise AnsibleError(
|
||||||
@@ -279,26 +249,19 @@ class KubernetesLookup(object):
|
|||||||
"using the 'resource_definition' parameter."
|
"using the 'resource_definition' parameter."
|
||||||
)
|
)
|
||||||
|
|
||||||
resource = self.client.resource(self.kind, self.api_version)
|
resource = self.find_resource(self.kind, self.api_version, fail=True)
|
||||||
try:
|
try:
|
||||||
params = dict(
|
k8s_obj = resource.get(name=self.name, namespace=self.namespace, label_selector=self.label_selector, field_selector=self.field_selector)
|
||||||
name=self.name,
|
|
||||||
namespace=self.namespace,
|
|
||||||
label_selector=self.label_selector,
|
|
||||||
field_selector=self.field_selector,
|
|
||||||
)
|
|
||||||
k8s_obj = self.client.get(resource, **params)
|
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if self.name:
|
if self.name:
|
||||||
return [k8s_obj.to_dict()]
|
return [k8s_obj.to_dict()]
|
||||||
|
|
||||||
return k8s_obj.to_dict().get("items")
|
return k8s_obj.to_dict().get('items')
|
||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
class LookupModule(LookupBase):
|
||||||
def _run(self, terms, variables=None, **kwargs):
|
|
||||||
return KubernetesLookup().run(terms, variables=variables, **kwargs)
|
|
||||||
|
|
||||||
run = _run if not hasattr(LookupBase, "run_on_daemon") else LookupBase.run_on_daemon
|
def run(self, terms, variables=None, **kwargs):
|
||||||
|
return KubernetesLookup().run(terms, variables=variables, **kwargs)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def get_binary_from_path(name, opt_dirs=None):
|
|||||||
if opt_dirs is not None:
|
if opt_dirs is not None:
|
||||||
if not isinstance(opt_dirs, list):
|
if not isinstance(opt_dirs, list):
|
||||||
opt_dirs = [opt_dirs]
|
opt_dirs = [opt_dirs]
|
||||||
opt_arg["opt_dirs"] = opt_dirs
|
opt_arg['opt_dirs'] = opt_dirs
|
||||||
bin_path = get_bin_path(name, **opt_arg)
|
bin_path = get_bin_path(name, **opt_arg)
|
||||||
return bin_path
|
return bin_path
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -91,41 +91,30 @@ def run_command(command):
|
|||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
class LookupModule(LookupBase):
|
||||||
def run(
|
|
||||||
self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs
|
def run(self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs):
|
||||||
):
|
|
||||||
executable_path = binary_path
|
executable_path = binary_path
|
||||||
if executable_path is None:
|
if executable_path is None:
|
||||||
executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
|
executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
|
||||||
if executable_path is None:
|
if executable_path is None:
|
||||||
executable_path = get_binary_from_path(
|
executable_path = get_binary_from_path(name="kubectl", opt_dirs=opt_dirs)
|
||||||
name="kubectl", opt_dirs=opt_dirs
|
|
||||||
)
|
|
||||||
|
|
||||||
# validate that at least one tool was found
|
# validate that at least one tool was found
|
||||||
if executable_path is None:
|
if executable_path is None:
|
||||||
raise AnsibleLookupError(
|
raise AnsibleLookupError("Failed to find required executable 'kubectl' and 'kustomize' in paths")
|
||||||
"Failed to find required executable 'kubectl' and 'kustomize' in paths"
|
|
||||||
)
|
|
||||||
|
|
||||||
# check input directory
|
# check input directory
|
||||||
kustomization_dir = dir
|
kustomization_dir = dir
|
||||||
|
|
||||||
command = [executable_path]
|
command = [executable_path]
|
||||||
if executable_path.endswith("kustomize"):
|
if executable_path.endswith('kustomize'):
|
||||||
command += ["build", kustomization_dir]
|
command += ['build', kustomization_dir]
|
||||||
elif executable_path.endswith("kubectl"):
|
elif executable_path.endswith('kubectl'):
|
||||||
command += ["kustomize", kustomization_dir]
|
command += ['kustomize', kustomization_dir]
|
||||||
else:
|
else:
|
||||||
raise AnsibleLookupError(
|
raise AnsibleLookupError("unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(executable_path))
|
||||||
"unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(
|
|
||||||
executable_path
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
(out, err) = run_command(command)
|
(out, err) = run_command(command)
|
||||||
if err:
|
if err:
|
||||||
raise AnsibleLookupError(
|
raise AnsibleLookupError("kustomize command failed with: {0}".format(err.decode("utf-8")))
|
||||||
"kustomize command failed with: {0}".format(err.decode("utf-8"))
|
return [out.decode('utf-8')]
|
||||||
)
|
|
||||||
return [out.decode("utf-8")]
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ if enable_turbo_mode:
|
|||||||
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
|
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
|
||||||
AnsibleTurboModule as AnsibleModule,
|
AnsibleTurboModule as AnsibleModule,
|
||||||
) # noqa: F401
|
) # noqa: F401
|
||||||
|
|
||||||
AnsibleModule.collection_name = "kubernetes.core"
|
AnsibleModule.collection_name = "kubernetes.core"
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
||||||
|
|||||||
@@ -14,23 +14,13 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||||
ApplyException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
gather_versions,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
|
||||||
LooseVersion,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.dynamic.exceptions import NotFoundError
|
from kubernetes.dynamic.exceptions import NotFoundError
|
||||||
@@ -38,52 +28,50 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
LAST_APPLIED_CONFIG_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
|
LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
|
||||||
|
|
||||||
POD_SPEC_SUFFIXES = {
|
POD_SPEC_SUFFIXES = {
|
||||||
"containers": "name",
|
'containers': 'name',
|
||||||
"initContainers": "name",
|
'initContainers': 'name',
|
||||||
"ephemeralContainers": "name",
|
'ephemeralContainers': 'name',
|
||||||
"volumes": "name",
|
'volumes': 'name',
|
||||||
"imagePullSecrets": "name",
|
'imagePullSecrets': 'name',
|
||||||
"containers.volumeMounts": "mountPath",
|
'containers.volumeMounts': 'mountPath',
|
||||||
"containers.volumeDevices": "devicePath",
|
'containers.volumeDevices': 'devicePath',
|
||||||
"containers.env": "name",
|
'containers.env': 'name',
|
||||||
"containers.ports": "containerPort",
|
'containers.ports': 'containerPort',
|
||||||
"initContainers.volumeMounts": "mountPath",
|
'initContainers.volumeMounts': 'mountPath',
|
||||||
"initContainers.volumeDevices": "devicePath",
|
'initContainers.volumeDevices': 'devicePath',
|
||||||
"initContainers.env": "name",
|
'initContainers.env': 'name',
|
||||||
"initContainers.ports": "containerPort",
|
'initContainers.ports': 'containerPort',
|
||||||
"ephemeralContainers.volumeMounts": "mountPath",
|
'ephemeralContainers.volumeMounts': 'mountPath',
|
||||||
"ephemeralContainers.volumeDevices": "devicePath",
|
'ephemeralContainers.volumeDevices': 'devicePath',
|
||||||
"ephemeralContainers.env": "name",
|
'ephemeralContainers.env': 'name',
|
||||||
"ephemeralContainers.ports": "containerPort",
|
'ephemeralContainers.ports': 'containerPort',
|
||||||
}
|
}
|
||||||
|
|
||||||
POD_SPEC_PREFIXES = [
|
POD_SPEC_PREFIXES = [
|
||||||
"Pod.spec",
|
'Pod.spec',
|
||||||
"Deployment.spec.template.spec",
|
'Deployment.spec.template.spec',
|
||||||
"DaemonSet.spec.template.spec",
|
'DaemonSet.spec.template.spec',
|
||||||
"StatefulSet.spec.template.spec",
|
'StatefulSet.spec.template.spec',
|
||||||
"Job.spec.template.spec",
|
'Job.spec.template.spec',
|
||||||
"Cronjob.spec.jobTemplate.spec.template.spec",
|
'Cronjob.spec.jobTemplate.spec.template.spec',
|
||||||
]
|
]
|
||||||
|
|
||||||
# patch merge keys taken from generated.proto files under
|
# patch merge keys taken from generated.proto files under
|
||||||
# staging/src/k8s.io/api in kubernetes/kubernetes
|
# staging/src/k8s.io/api in kubernetes/kubernetes
|
||||||
STRATEGIC_MERGE_PATCH_KEYS = {
|
STRATEGIC_MERGE_PATCH_KEYS = {
|
||||||
"Service.spec.ports": "port",
|
'Service.spec.ports': 'port',
|
||||||
"ServiceAccount.secrets": "name",
|
'ServiceAccount.secrets': 'name',
|
||||||
"ValidatingWebhookConfiguration.webhooks": "name",
|
'ValidatingWebhookConfiguration.webhooks': 'name',
|
||||||
"MutatingWebhookConfiguration.webhooks": "name",
|
'MutatingWebhookConfiguration.webhooks': 'name',
|
||||||
}
|
}
|
||||||
|
|
||||||
STRATEGIC_MERGE_PATCH_KEYS.update(
|
STRATEGIC_MERGE_PATCH_KEYS.update(
|
||||||
{
|
{"%s.%s" % (prefix, key): value
|
||||||
"%s.%s" % (prefix, key): value
|
for prefix in POD_SPEC_PREFIXES
|
||||||
for prefix in POD_SPEC_PREFIXES
|
for key, value in POD_SPEC_SUFFIXES.items()}
|
||||||
for key, value in POD_SPEC_SUFFIXES.items()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -91,28 +79,21 @@ def annotate(desired):
|
|||||||
return dict(
|
return dict(
|
||||||
metadata=dict(
|
metadata=dict(
|
||||||
annotations={
|
annotations={
|
||||||
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(
|
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True)
|
||||||
desired, separators=(",", ":"), indent=None, sort_keys=True
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_patch(actual, desired):
|
def apply_patch(actual, desired):
|
||||||
last_applied = (
|
last_applied = actual['metadata'].get('annotations', {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
|
||||||
actual["metadata"].get("annotations", {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_applied:
|
if last_applied:
|
||||||
# ensure that last_applied doesn't come back as a dict of unicode key/value pairs
|
# ensure that last_applied doesn't come back as a dict of unicode key/value pairs
|
||||||
# json.loads can be used if we stop supporting python 2
|
# json.loads can be used if we stop supporting python 2
|
||||||
last_applied = json.loads(last_applied)
|
last_applied = json.loads(last_applied)
|
||||||
patch = merge(
|
patch = merge(dict_merge(last_applied, annotate(last_applied)),
|
||||||
dict_merge(last_applied, annotate(last_applied)),
|
dict_merge(desired, annotate(desired)), actual)
|
||||||
dict_merge(desired, annotate(desired)),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
if patch:
|
if patch:
|
||||||
return actual, patch
|
return actual, patch
|
||||||
else:
|
else:
|
||||||
@@ -121,52 +102,24 @@ def apply_patch(actual, desired):
|
|||||||
return actual, dict_merge(desired, annotate(desired))
|
return actual, dict_merge(desired, annotate(desired))
|
||||||
|
|
||||||
|
|
||||||
def apply_object(resource, definition, server_side=False):
|
def apply_object(resource, definition):
|
||||||
try:
|
try:
|
||||||
actual = resource.get(
|
actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
||||||
name=definition["metadata"]["name"],
|
|
||||||
namespace=definition["metadata"].get("namespace"),
|
|
||||||
)
|
|
||||||
if server_side:
|
|
||||||
return actual, None
|
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return None, dict_merge(definition, annotate(definition))
|
return None, dict_merge(definition, annotate(definition))
|
||||||
return apply_patch(actual.to_dict(), definition)
|
return apply_patch(actual.to_dict(), definition)
|
||||||
|
|
||||||
|
|
||||||
def k8s_apply(resource, definition, **kwargs):
|
def k8s_apply(resource, definition):
|
||||||
existing, desired = apply_object(resource, definition)
|
existing, desired = apply_object(resource, definition)
|
||||||
server_side = kwargs.get("server_side", False)
|
|
||||||
if server_side:
|
|
||||||
versions = gather_versions()
|
|
||||||
body = definition
|
|
||||||
if LooseVersion(versions["kubernetes"]) < LooseVersion("25.0.0"):
|
|
||||||
body = json.dumps(definition).encode()
|
|
||||||
# server_side_apply is forces content_type to 'application/apply-patch+yaml'
|
|
||||||
return resource.server_side_apply(
|
|
||||||
body=body,
|
|
||||||
name=definition["metadata"]["name"],
|
|
||||||
namespace=definition["metadata"].get("namespace"),
|
|
||||||
force_conflicts=kwargs.get("force_conflicts"),
|
|
||||||
field_manager=kwargs.get("field_manager"),
|
|
||||||
dry_run=kwargs.get("dry_run"),
|
|
||||||
)
|
|
||||||
if not existing:
|
if not existing:
|
||||||
return resource.create(
|
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
|
||||||
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
|
|
||||||
)
|
|
||||||
if existing == desired:
|
if existing == desired:
|
||||||
return resource.get(
|
return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
||||||
name=definition["metadata"]["name"],
|
return resource.patch(body=desired,
|
||||||
namespace=definition["metadata"].get("namespace"),
|
name=definition['metadata']['name'],
|
||||||
)
|
namespace=definition['metadata'].get('namespace'),
|
||||||
return resource.patch(
|
content_type='application/merge-patch+json')
|
||||||
body=desired,
|
|
||||||
name=definition["metadata"]["name"],
|
|
||||||
namespace=definition["metadata"].get("namespace"),
|
|
||||||
content_type="application/merge-patch+json",
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# The patch is the difference from actual to desired without deletions, plus deletions
|
# The patch is the difference from actual to desired without deletions, plus deletions
|
||||||
@@ -175,7 +128,7 @@ def k8s_apply(resource, definition, **kwargs):
|
|||||||
# deletions, and then apply delta to deletions as a patch, which should be strictly additive.
|
# deletions, and then apply delta to deletions as a patch, which should be strictly additive.
|
||||||
def merge(last_applied, desired, actual, position=None):
|
def merge(last_applied, desired, actual, position=None):
|
||||||
deletions = get_deletions(last_applied, desired)
|
deletions = get_deletions(last_applied, desired)
|
||||||
delta = get_delta(last_applied, actual, desired, position or desired["kind"])
|
delta = get_delta(last_applied, actual, desired, position or desired['kind'])
|
||||||
return dict_merge(deletions, delta)
|
return dict_merge(deletions, delta)
|
||||||
|
|
||||||
|
|
||||||
@@ -185,9 +138,7 @@ def list_to_dict(lst, key, position):
|
|||||||
try:
|
try:
|
||||||
result[item[key]] = item
|
result[item[key]] = item
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ApplyException(
|
raise ApplyException("Expected key '%s' not found in position %s" % (key, position))
|
||||||
"Expected key '%s' not found in position %s" % (key, position)
|
|
||||||
)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -206,12 +157,7 @@ def list_merge(last_applied, actual, desired, position):
|
|||||||
if key not in actual_dict or key not in last_applied_dict:
|
if key not in actual_dict or key not in last_applied_dict:
|
||||||
result.append(desired_dict[key])
|
result.append(desired_dict[key])
|
||||||
else:
|
else:
|
||||||
patch = merge(
|
patch = merge(last_applied_dict[key], desired_dict[key], actual_dict[key], position)
|
||||||
last_applied_dict[key],
|
|
||||||
desired_dict[key],
|
|
||||||
actual_dict[key],
|
|
||||||
position,
|
|
||||||
)
|
|
||||||
result.append(dict_merge(actual_dict[key], patch))
|
result.append(dict_merge(actual_dict[key], patch))
|
||||||
for key in actual_dict:
|
for key in actual_dict:
|
||||||
if key not in desired_dict and key not in last_applied_dict:
|
if key not in desired_dict and key not in last_applied_dict:
|
||||||
@@ -251,11 +197,11 @@ def recursive_list_diff(list1, list2, position=None):
|
|||||||
|
|
||||||
def recursive_diff(dict1, dict2, position=None):
|
def recursive_diff(dict1, dict2, position=None):
|
||||||
if not position:
|
if not position:
|
||||||
if "kind" in dict1 and dict1.get("kind") == dict2.get("kind"):
|
if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
|
||||||
position = dict1["kind"]
|
position = dict1['kind']
|
||||||
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
|
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
|
||||||
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
|
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
|
||||||
for k in set(dict1.keys()) & set(dict2.keys()):
|
for k in (set(dict1.keys()) & set(dict2.keys())):
|
||||||
if position:
|
if position:
|
||||||
this_position = "%s.%s" % (position, k)
|
this_position = "%s.%s" % (position, k)
|
||||||
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
|
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
|
||||||
@@ -300,15 +246,11 @@ def get_delta(last_applied, actual, desired, position=None):
|
|||||||
if actual_value is None:
|
if actual_value is None:
|
||||||
patch[k] = desired_value
|
patch[k] = desired_value
|
||||||
elif isinstance(desired_value, dict):
|
elif isinstance(desired_value, dict):
|
||||||
p = get_delta(
|
p = get_delta(last_applied.get(k, {}), actual_value, desired_value, this_position)
|
||||||
last_applied.get(k, {}), actual_value, desired_value, this_position
|
|
||||||
)
|
|
||||||
if p:
|
if p:
|
||||||
patch[k] = p
|
patch[k] = p
|
||||||
elif isinstance(desired_value, list):
|
elif isinstance(desired_value, list):
|
||||||
p = list_merge(
|
p = list_merge(last_applied.get(k, []), actual_value, desired_value, this_position)
|
||||||
last_applied.get(k, []), actual_value, desired_value, this_position
|
|
||||||
)
|
|
||||||
if p:
|
if p:
|
||||||
patch[k] = [item for item in p if item is not None]
|
patch[k] = [item for item in p if item is not None]
|
||||||
elif actual_value != desired_value:
|
elif actual_value != desired_value:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
@@ -12,87 +12,133 @@ def list_dict_str(value):
|
|||||||
|
|
||||||
|
|
||||||
AUTH_PROXY_HEADERS_SPEC = dict(
|
AUTH_PROXY_HEADERS_SPEC = dict(
|
||||||
proxy_basic_auth=dict(type="str", no_log=True),
|
proxy_basic_auth=dict(type='str', no_log=True),
|
||||||
basic_auth=dict(type="str", no_log=True),
|
basic_auth=dict(type='str', no_log=True),
|
||||||
user_agent=dict(type="str"),
|
user_agent=dict(type='str')
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTH_ARG_SPEC = {
|
AUTH_ARG_SPEC = {
|
||||||
"kubeconfig": {"type": "raw"},
|
'kubeconfig': {
|
||||||
"context": {},
|
'type': 'raw',
|
||||||
"host": {},
|
},
|
||||||
"api_key": {"no_log": True},
|
'context': {},
|
||||||
"username": {},
|
'host': {},
|
||||||
"password": {"no_log": True},
|
'api_key': {
|
||||||
"validate_certs": {"type": "bool", "aliases": ["verify_ssl"]},
|
'no_log': True,
|
||||||
"ca_cert": {"type": "path", "aliases": ["ssl_ca_cert"]},
|
},
|
||||||
"client_cert": {"type": "path", "aliases": ["cert_file"]},
|
'username': {},
|
||||||
"client_key": {"type": "path", "aliases": ["key_file"]},
|
'password': {
|
||||||
"proxy": {"type": "str"},
|
'no_log': True,
|
||||||
"no_proxy": {"type": "str"},
|
},
|
||||||
"proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC},
|
'validate_certs': {
|
||||||
"persist_config": {"type": "bool"},
|
'type': 'bool',
|
||||||
"impersonate_user": {},
|
'aliases': ['verify_ssl'],
|
||||||
"impersonate_groups": {"type": "list", "elements": "str"},
|
},
|
||||||
|
'ca_cert': {
|
||||||
|
'type': 'path',
|
||||||
|
'aliases': ['ssl_ca_cert'],
|
||||||
|
},
|
||||||
|
'client_cert': {
|
||||||
|
'type': 'path',
|
||||||
|
'aliases': ['cert_file'],
|
||||||
|
},
|
||||||
|
'client_key': {
|
||||||
|
'type': 'path',
|
||||||
|
'aliases': ['key_file'],
|
||||||
|
},
|
||||||
|
'proxy': {
|
||||||
|
'type': 'str',
|
||||||
|
},
|
||||||
|
'proxy_headers': {
|
||||||
|
'type': 'dict',
|
||||||
|
'options': AUTH_PROXY_HEADERS_SPEC
|
||||||
|
},
|
||||||
|
'persist_config': {
|
||||||
|
'type': 'bool',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
WAIT_ARG_SPEC = dict(
|
WAIT_ARG_SPEC = dict(
|
||||||
wait=dict(type="bool", default=False),
|
wait=dict(type='bool', default=False),
|
||||||
wait_sleep=dict(type="int", default=5),
|
wait_sleep=dict(type='int', default=5),
|
||||||
wait_timeout=dict(type="int", default=120),
|
wait_timeout=dict(type='int', default=120),
|
||||||
wait_condition=dict(
|
wait_condition=dict(
|
||||||
type="dict",
|
type='dict',
|
||||||
default=None,
|
default=None,
|
||||||
options=dict(
|
options=dict(
|
||||||
type=dict(),
|
type=dict(),
|
||||||
status=dict(default=True, choices=[True, False, "Unknown"]),
|
status=dict(default=True, choices=[True, False, "Unknown"]),
|
||||||
reason=dict(),
|
reason=dict()
|
||||||
),
|
)
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Map kubernetes-client parameters to ansible parameters
|
# Map kubernetes-client parameters to ansible parameters
|
||||||
AUTH_ARG_MAP = {
|
AUTH_ARG_MAP = {
|
||||||
"kubeconfig": "kubeconfig",
|
'kubeconfig': 'kubeconfig',
|
||||||
"context": "context",
|
'context': 'context',
|
||||||
"host": "host",
|
'host': 'host',
|
||||||
"api_key": "api_key",
|
'api_key': 'api_key',
|
||||||
"username": "username",
|
'username': 'username',
|
||||||
"password": "password",
|
'password': 'password',
|
||||||
"verify_ssl": "validate_certs",
|
'verify_ssl': 'validate_certs',
|
||||||
"ssl_ca_cert": "ca_cert",
|
'ssl_ca_cert': 'ca_cert',
|
||||||
"cert_file": "client_cert",
|
'cert_file': 'client_cert',
|
||||||
"key_file": "client_key",
|
'key_file': 'client_key',
|
||||||
"proxy": "proxy",
|
'proxy': 'proxy',
|
||||||
"no_proxy": "no_proxy",
|
'proxy_headers': 'proxy_headers',
|
||||||
"proxy_headers": "proxy_headers",
|
'persist_config': 'persist_config',
|
||||||
"persist_config": "persist_config",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NAME_ARG_SPEC = {
|
NAME_ARG_SPEC = {
|
||||||
"kind": {},
|
'kind': {},
|
||||||
"name": {},
|
'name': {},
|
||||||
"namespace": {},
|
'namespace': {},
|
||||||
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
'api_version': {
|
||||||
|
'default': 'v1',
|
||||||
|
'aliases': ['api', 'version'],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMON_ARG_SPEC = {
|
COMMON_ARG_SPEC = {
|
||||||
"state": {"default": "present", "choices": ["present", "absent"]},
|
'state': {
|
||||||
"force": {"type": "bool", "default": False},
|
'default': 'present',
|
||||||
|
'choices': ['present', 'absent'],
|
||||||
|
},
|
||||||
|
'force': {
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RESOURCE_ARG_SPEC = {
|
RESOURCE_ARG_SPEC = {
|
||||||
"resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]},
|
'resource_definition': {
|
||||||
"src": {"type": "path"},
|
'type': list_dict_str,
|
||||||
}
|
'aliases': ['definition', 'inline']
|
||||||
|
},
|
||||||
ARG_ATTRIBUTES_BLACKLIST = ("property_path",)
|
'src': {
|
||||||
|
'type': 'path',
|
||||||
DELETE_OPTS_ARG_SPEC = {
|
|
||||||
"propagationPolicy": {"choices": ["Foreground", "Background", "Orphan"]},
|
|
||||||
"gracePeriodSeconds": {"type": "int"},
|
|
||||||
"preconditions": {
|
|
||||||
"type": "dict",
|
|
||||||
"options": {"resourceVersion": {"type": "str"}, "uid": {"type": "str"}},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
|
||||||
|
|
||||||
|
DELETE_OPTS_ARG_SPEC = {
|
||||||
|
'propagationPolicy': {
|
||||||
|
'choices': ['Foreground', 'Background', 'Orphan'],
|
||||||
|
},
|
||||||
|
'gracePeriodSeconds': {
|
||||||
|
'type': 'int',
|
||||||
|
},
|
||||||
|
'preconditions': {
|
||||||
|
'type': 'dict',
|
||||||
|
'options': {
|
||||||
|
'resourceVersion': {
|
||||||
|
'type': 'str',
|
||||||
|
},
|
||||||
|
'uid': {
|
||||||
|
'type': 'str',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,26 +23,17 @@ from functools import partial
|
|||||||
import kubernetes.dynamic
|
import kubernetes.dynamic
|
||||||
import kubernetes.dynamic.discovery
|
import kubernetes.dynamic.discovery
|
||||||
from kubernetes import __version__
|
from kubernetes import __version__
|
||||||
from kubernetes.dynamic.exceptions import (
|
from kubernetes.dynamic.exceptions import (ResourceNotFoundError, ResourceNotUniqueError,
|
||||||
ResourceNotFoundError,
|
ServiceUnavailableError)
|
||||||
ResourceNotUniqueError,
|
|
||||||
ServiceUnavailableError,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
||||||
ResourceList,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||||
def __init__(self, client, cache_file):
|
def __init__(self, client, cache_file):
|
||||||
self.client = client
|
self.client = client
|
||||||
default_cache_file_name = "k8srcp-{0}.json".format(
|
default_cache_file_name = 'k8srcp-{0}.json'.format(hashlib.sha256(self.__get_default_cache_id()).hexdigest())
|
||||||
hashlib.sha256(self.__get_default_cache_id()).hexdigest()
|
self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cache_file_name)
|
||||||
)
|
|
||||||
self.__cache_file = cache_file or os.path.join(
|
|
||||||
tempfile.gettempdir(), default_cache_file_name
|
|
||||||
)
|
|
||||||
self.__init_cache()
|
self.__init_cache()
|
||||||
|
|
||||||
def __get_default_cache_id(self):
|
def __get_default_cache_id(self):
|
||||||
@@ -51,21 +42,21 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
cache_id = "{0}-{1}".format(self.client.configuration.host, user)
|
cache_id = "{0}-{1}".format(self.client.configuration.host, user)
|
||||||
else:
|
else:
|
||||||
cache_id = self.client.configuration.host
|
cache_id = self.client.configuration.host
|
||||||
return cache_id.encode("utf-8")
|
return cache_id.encode('utf-8')
|
||||||
|
|
||||||
def __get_user(self):
|
def __get_user(self):
|
||||||
# This is intended to provide a portable method for getting a username.
|
# This is intended to provide a portable method for getting a username.
|
||||||
# It could, and maybe should, be replaced by getpass.getuser() but, due
|
# It could, and maybe should, be replaced by getpass.getuser() but, due
|
||||||
# to a lack of portability testing the original code is being left in
|
# to a lack of portability testing the original code is being left in
|
||||||
# place.
|
# place.
|
||||||
if hasattr(os, "getlogin"):
|
if hasattr(os, 'getlogin'):
|
||||||
try:
|
try:
|
||||||
user = os.getlogin()
|
user = os.getlogin()
|
||||||
if user:
|
if user:
|
||||||
return str(user)
|
return str(user)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if hasattr(os, "getuid"):
|
if hasattr(os, 'getuid'):
|
||||||
try:
|
try:
|
||||||
user = os.getuid()
|
user = os.getuid()
|
||||||
if user:
|
if user:
|
||||||
@@ -79,13 +70,13 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
|
|
||||||
def __init_cache(self, refresh=False):
|
def __init_cache(self, refresh=False):
|
||||||
if refresh or not os.path.exists(self.__cache_file):
|
if refresh or not os.path.exists(self.__cache_file):
|
||||||
self._cache = {"library_version": __version__}
|
self._cache = {'library_version': __version__}
|
||||||
refresh = True
|
refresh = True
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
with open(self.__cache_file, "r") as f:
|
with open(self.__cache_file, 'r') as f:
|
||||||
self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
|
self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
|
||||||
if self._cache.get("library_version") != __version__:
|
if self._cache.get('library_version') != __version__:
|
||||||
# Version mismatch, need to refresh cache
|
# Version mismatch, need to refresh cache
|
||||||
self.invalidate_cache()
|
self.invalidate_cache()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -96,30 +87,26 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
self._write_cache()
|
self._write_cache()
|
||||||
|
|
||||||
def get_resources_for_api_version(self, prefix, group, version, preferred):
|
def get_resources_for_api_version(self, prefix, group, version, preferred):
|
||||||
"""returns a dictionary of resources associated with provided (prefix, group, version)"""
|
""" returns a dictionary of resources associated with provided (prefix, group, version)"""
|
||||||
|
|
||||||
resources = defaultdict(list)
|
resources = defaultdict(list)
|
||||||
subresources = defaultdict(dict)
|
subresources = defaultdict(dict)
|
||||||
|
|
||||||
path = "/".join(filter(None, [prefix, group, version]))
|
path = '/'.join(filter(None, [prefix, group, version]))
|
||||||
try:
|
try:
|
||||||
resources_response = self.client.request("GET", path).resources or []
|
resources_response = self.client.request('GET', path).resources or []
|
||||||
except ServiceUnavailableError:
|
except ServiceUnavailableError:
|
||||||
resources_response = []
|
resources_response = []
|
||||||
|
|
||||||
resources_raw = list(
|
resources_raw = list(filter(lambda resource: '/' not in resource['name'], resources_response))
|
||||||
filter(lambda resource: "/" not in resource["name"], resources_response)
|
subresources_raw = list(filter(lambda resource: '/' in resource['name'], resources_response))
|
||||||
)
|
|
||||||
subresources_raw = list(
|
|
||||||
filter(lambda resource: "/" in resource["name"], resources_response)
|
|
||||||
)
|
|
||||||
for subresource in subresources_raw:
|
for subresource in subresources_raw:
|
||||||
resource, name = subresource["name"].split("/")
|
resource, name = subresource['name'].split('/')
|
||||||
subresources[resource][name] = subresource
|
subresources[resource][name] = subresource
|
||||||
|
|
||||||
for resource in resources_raw:
|
for resource in resources_raw:
|
||||||
# Prevent duplicate keys
|
# Prevent duplicate keys
|
||||||
for key in ("prefix", "group", "api_version", "client", "preferred"):
|
for key in ('prefix', 'group', 'api_version', 'client', 'preferred'):
|
||||||
resource.pop(key, None)
|
resource.pop(key, None)
|
||||||
|
|
||||||
resourceobj = kubernetes.dynamic.Resource(
|
resourceobj = kubernetes.dynamic.Resource(
|
||||||
@@ -128,25 +115,19 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
api_version=version,
|
api_version=version,
|
||||||
client=self.client,
|
client=self.client,
|
||||||
preferred=preferred,
|
preferred=preferred,
|
||||||
subresources=subresources.get(resource["name"]),
|
subresources=subresources.get(resource['name']),
|
||||||
**resource
|
**resource
|
||||||
)
|
)
|
||||||
resources[resource["kind"]].append(resourceobj)
|
resources[resource['kind']].append(resourceobj)
|
||||||
|
|
||||||
resource_lookup = {
|
resource_lookup = {
|
||||||
"prefix": prefix,
|
'prefix': prefix,
|
||||||
"group": group,
|
'group': group,
|
||||||
"api_version": version,
|
'api_version': version,
|
||||||
"kind": resourceobj.kind,
|
'kind': resourceobj.kind,
|
||||||
"name": resourceobj.name,
|
'name': resourceobj.name
|
||||||
}
|
}
|
||||||
resource_list = ResourceList(
|
resource_list = ResourceList(self.client, group=group, api_version=version, base_kind=resource['kind'], base_resource_lookup=resource_lookup)
|
||||||
self.client,
|
|
||||||
group=group,
|
|
||||||
api_version=version,
|
|
||||||
base_kind=resource["kind"],
|
|
||||||
base_resource_lookup=resource_lookup,
|
|
||||||
)
|
|
||||||
resources[resource_list.kind].append(resource_list)
|
resources[resource_list.kind].append(resource_list)
|
||||||
return resources
|
return resources
|
||||||
|
|
||||||
@@ -158,32 +139,23 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
"""
|
"""
|
||||||
results = self.search(**kwargs)
|
results = self.search(**kwargs)
|
||||||
# If there are multiple matches, prefer exact matches on api_version
|
# If there are multiple matches, prefer exact matches on api_version
|
||||||
if len(results) > 1 and kwargs.get("api_version"):
|
if len(results) > 1 and kwargs.get('api_version'):
|
||||||
results = [
|
results = [
|
||||||
result
|
result for result in results if result.group_version == kwargs['api_version']
|
||||||
for result in results
|
|
||||||
if result.group_version == kwargs["api_version"]
|
|
||||||
]
|
]
|
||||||
# If there are multiple matches, prefer non-List kinds
|
# If there are multiple matches, prefer non-List kinds
|
||||||
if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results):
|
if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results):
|
||||||
results = [
|
results = [result for result in results if not isinstance(result, ResourceList)]
|
||||||
result for result in results if not isinstance(result, ResourceList)
|
|
||||||
]
|
|
||||||
# if multiple resources are found that share a GVK, prefer the one with the most supported verbs
|
# if multiple resources are found that share a GVK, prefer the one with the most supported verbs
|
||||||
if (
|
if len(results) > 1 and len(set((x.group_version, x.kind) for x in results)) == 1:
|
||||||
len(results) > 1
|
|
||||||
and len(set((x.group_version, x.kind) for x in results)) == 1
|
|
||||||
):
|
|
||||||
if len(set(len(x.verbs) for x in results)) != 1:
|
if len(set(len(x.verbs) for x in results)) != 1:
|
||||||
results = [max(results, key=lambda x: len(x.verbs))]
|
results = [max(results, key=lambda x: len(x.verbs))]
|
||||||
if len(results) == 1:
|
if len(results) == 1:
|
||||||
return results[0]
|
return results[0]
|
||||||
elif not results:
|
elif not results:
|
||||||
raise ResourceNotFoundError("No matches found for {0}".format(kwargs))
|
raise ResourceNotFoundError('No matches found for {0}'.format(kwargs))
|
||||||
else:
|
else:
|
||||||
raise ResourceNotUniqueError(
|
raise ResourceNotUniqueError('Multiple matches found for {0}: {1}'.format(kwargs, results))
|
||||||
"Multiple matches found for {0}: {1}".format(kwargs, results)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
|
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
|
||||||
@@ -202,15 +174,13 @@ class CacheDecoder(json.JSONDecoder):
|
|||||||
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
|
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
|
||||||
|
|
||||||
def object_hook(self, obj):
|
def object_hook(self, obj):
|
||||||
if "_type" not in obj:
|
if '_type' not in obj:
|
||||||
return obj
|
return obj
|
||||||
_type = obj.pop("_type")
|
_type = obj.pop('_type')
|
||||||
if _type == "Resource":
|
if _type == 'Resource':
|
||||||
return kubernetes.dynamic.Resource(client=self.client, **obj)
|
return kubernetes.dynamic.Resource(client=self.client, **obj)
|
||||||
elif _type == "ResourceList":
|
elif _type == 'ResourceList':
|
||||||
return ResourceList(self.client, **obj)
|
return ResourceList(self.client, **obj)
|
||||||
elif _type == "ResourceGroup":
|
elif _type == 'ResourceGroup':
|
||||||
return kubernetes.dynamic.discovery.ResourceGroup(
|
return kubernetes.dynamic.discovery.ResourceGroup(obj['preferred'], resources=self.object_hook(obj['resources']))
|
||||||
obj["preferred"], resources=self.object_hook(obj["resources"])
|
|
||||||
)
|
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
@@ -22,19 +21,11 @@ import kubernetes.dynamic
|
|||||||
|
|
||||||
|
|
||||||
class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
||||||
def __init__(
|
def __init__(self, client, group='', api_version='v1', base_kind='', kind=None, base_resource_lookup=None):
|
||||||
self,
|
|
||||||
client,
|
|
||||||
group="",
|
|
||||||
api_version="v1",
|
|
||||||
base_kind="",
|
|
||||||
kind=None,
|
|
||||||
base_resource_lookup=None,
|
|
||||||
):
|
|
||||||
self.client = client
|
self.client = client
|
||||||
self.group = group
|
self.group = group
|
||||||
self.api_version = api_version
|
self.api_version = api_version
|
||||||
self.kind = kind or "{0}List".format(base_kind)
|
self.kind = kind or '{0}List'.format(base_kind)
|
||||||
self.base_kind = base_kind
|
self.base_kind = base_kind
|
||||||
self.base_resource_lookup = base_resource_lookup
|
self.base_resource_lookup = base_resource_lookup
|
||||||
self.__base_resource = None
|
self.__base_resource = None
|
||||||
@@ -43,18 +34,16 @@ class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
|||||||
if self.__base_resource:
|
if self.__base_resource:
|
||||||
return self.__base_resource
|
return self.__base_resource
|
||||||
elif self.base_resource_lookup:
|
elif self.base_resource_lookup:
|
||||||
self.__base_resource = self.client.resources.get(
|
self.__base_resource = self.client.resources.get(**self.base_resource_lookup)
|
||||||
**self.base_resource_lookup
|
|
||||||
)
|
|
||||||
return self.__base_resource
|
return self.__base_resource
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
"_type": "ResourceList",
|
'_type': 'ResourceList',
|
||||||
"group": self.group,
|
'group': self.group,
|
||||||
"api_version": self.api_version,
|
'api_version': self.api_version,
|
||||||
"kind": self.kind,
|
'kind': self.kind,
|
||||||
"base_kind": self.base_kind,
|
'base_kind': self.base_kind,
|
||||||
"base_resource_lookup": self.base_resource_lookup,
|
'base_resource_lookup': self.base_resource_lookup
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,444 +0,0 @@
|
|||||||
# Copyright [2021] [Red Hat, Inc.]
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import os
|
|
||||||
from tempfile import TemporaryFile, NamedTemporaryFile
|
|
||||||
from select import select
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
import tarfile
|
|
||||||
|
|
||||||
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible.module_utils._text import to_native
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
except ImportError:
|
|
||||||
# ImportError are managed by the common module already.
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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.no_preserve = module.params.get("no_preserve")
|
|
||||||
self.container_arg = {}
|
|
||||||
if module.params.get("container"):
|
|
||||||
self.container_arg["container"] = module.params.get("container")
|
|
||||||
self.check_mode = self.module.check_mode
|
|
||||||
|
|
||||||
def _run_from_pod(self, cmd):
|
|
||||||
try:
|
|
||||||
resp = stream(
|
|
||||||
self.api_instance.connect_get_namespaced_pod_exec,
|
|
||||||
self.name,
|
|
||||||
self.namespace,
|
|
||||||
command=cmd,
|
|
||||||
async_req=False,
|
|
||||||
stderr=True,
|
|
||||||
stdin=False,
|
|
||||||
stdout=True,
|
|
||||||
tty=False,
|
|
||||||
_preload_content=False,
|
|
||||||
**self.container_arg,
|
|
||||||
)
|
|
||||||
|
|
||||||
stderr, stdout = [], []
|
|
||||||
while resp.is_open():
|
|
||||||
resp.update(timeout=1)
|
|
||||||
if resp.peek_stdout():
|
|
||||||
stdout.extend(resp.read_stdout().rstrip("\n").split("\n"))
|
|
||||||
if resp.peek_stderr():
|
|
||||||
stderr.extend(resp.read_stderr().rstrip("\n").split("\n"))
|
|
||||||
error = resp.read_channel(ERROR_CHANNEL)
|
|
||||||
resp.close()
|
|
||||||
error = yaml.safe_load(error)
|
|
||||||
return error, stdout, stderr
|
|
||||||
except Exception as e:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg="Error while running/parsing from pod {1}/{2} command='{0}' : {3}".format(
|
|
||||||
self.namespace, self.name, cmd, to_native(e)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_directory_path_from_pod(self, file_path, failed_if_not_exists=True):
|
|
||||||
# check if file exists
|
|
||||||
error, out, err = self._run_from_pod(cmd=["test", "-e", file_path])
|
|
||||||
if error.get("status") != "Success":
|
|
||||||
if failed_if_not_exists:
|
|
||||||
return None, "%s does not exist in remote pod filesystem" % file_path
|
|
||||||
return False, None
|
|
||||||
error, out, err = self._run_from_pod(cmd=["test", "-d", file_path])
|
|
||||||
return error.get("status") == "Success", None
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def run(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
self.files_to_copy = []
|
|
||||||
self._shellname = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pod_shell(self):
|
|
||||||
if self._shellname is None:
|
|
||||||
for s in ("/bin/sh", "/bin/bash"):
|
|
||||||
error, out, err = self._run_from_pod(s)
|
|
||||||
if error.get("status") == "Success":
|
|
||||||
self._shellname = s
|
|
||||||
break
|
|
||||||
return self._shellname
|
|
||||||
|
|
||||||
def listfiles_with_find(self, path):
|
|
||||||
find_cmd = ["find", path, "-type", "f"]
|
|
||||||
error, files, err = self._run_from_pod(cmd=find_cmd)
|
|
||||||
if error.get("status") != "Success":
|
|
||||||
self.module.fail_json(msg=error.get("message"))
|
|
||||||
return files
|
|
||||||
|
|
||||||
def listfile_with_echo(self, path):
|
|
||||||
echo_cmd = [
|
|
||||||
self.pod_shell,
|
|
||||||
"-c",
|
|
||||||
"echo {path}/* {path}/.*".format(
|
|
||||||
path=path.translate(str.maketrans({" ": r"\ "}))
|
|
||||||
),
|
|
||||||
]
|
|
||||||
error, out, err = self._run_from_pod(cmd=echo_cmd)
|
|
||||||
if error.get("status") != "Success":
|
|
||||||
self.module.fail_json(msg=error.get("message"))
|
|
||||||
|
|
||||||
files = []
|
|
||||||
if out:
|
|
||||||
output = out[0] + " "
|
|
||||||
files = [
|
|
||||||
os.path.join(path, p[:-1])
|
|
||||||
for p in output.split(f"{path}/")
|
|
||||||
if p and p[:-1] not in (".", "..")
|
|
||||||
]
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for f in files:
|
|
||||||
is_dir, err = self.is_directory_path_from_pod(f)
|
|
||||||
if err:
|
|
||||||
continue
|
|
||||||
if not is_dir:
|
|
||||||
result.append(f)
|
|
||||||
continue
|
|
||||||
result += self.listfile_with_echo(f)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def list_remote_files(self):
|
|
||||||
"""
|
|
||||||
This method will check if the remote path is a dir or file
|
|
||||||
if it is a directory the file list will be updated accordingly
|
|
||||||
"""
|
|
||||||
# check is remote path exists and is a file or directory
|
|
||||||
is_dir, error = self.is_directory_path_from_pod(self.remote_path)
|
|
||||||
if error:
|
|
||||||
self.module.fail_json(msg=error)
|
|
||||||
|
|
||||||
if not is_dir:
|
|
||||||
return [self.remote_path]
|
|
||||||
else:
|
|
||||||
# find executable to list dir with
|
|
||||||
executables = dict(
|
|
||||||
find=self.listfiles_with_find,
|
|
||||||
echo=self.listfile_with_echo,
|
|
||||||
)
|
|
||||||
for item in executables:
|
|
||||||
error, out, err = self._run_from_pod(item)
|
|
||||||
if error.get("status") == "Success":
|
|
||||||
return executables.get(item)(self.remote_path)
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
self.stdout = None
|
|
||||||
self.stderr = None
|
|
||||||
|
|
||||||
if self.response.is_open():
|
|
||||||
if not self.response.sock.connected:
|
|
||||||
self.response._connected = False
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
channel = frame.data[0]
|
|
||||||
content = frame.data[1:]
|
|
||||||
if content:
|
|
||||||
if channel == STDOUT_CHANNEL:
|
|
||||||
self.stdout = content
|
|
||||||
elif channel == STDERR_CHANNEL:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
|
|
||||||
if not self.check_mode:
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
# 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,
|
|
||||||
)
|
|
||||||
errors = []
|
|
||||||
with open(dest_file, "wb") as fh:
|
|
||||||
while self.response._connected:
|
|
||||||
self.read()
|
|
||||||
if self.stdout:
|
|
||||||
fh.write(self.stdout)
|
|
||||||
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
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.files_to_copy = 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.copy()
|
|
||||||
|
|
||||||
|
|
||||||
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 close_temp_file(self):
|
|
||||||
if self.named_temp_file:
|
|
||||||
self.named_temp_file.close()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# remove trailing slash from destination path
|
|
||||||
dest_file = self.remote_path.rstrip("/")
|
|
||||||
src_file = self.local_path
|
|
||||||
self.named_temp_file = None
|
|
||||||
if self.content:
|
|
||||||
self.named_temp_file = NamedTemporaryFile(mode="w")
|
|
||||||
self.named_temp_file.write(self.content)
|
|
||||||
self.named_temp_file.flush()
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
if not os.access(self.local_path, os.R_OK):
|
|
||||||
self.module.fail_json(msg="{0} not readable".format(self.local_path))
|
|
||||||
|
|
||||||
is_dir, err = self.is_directory_path_from_pod(
|
|
||||||
self.remote_path, failed_if_not_exists=False
|
|
||||||
)
|
|
||||||
if err:
|
|
||||||
self.module.fail_json(msg=err)
|
|
||||||
if is_dir:
|
|
||||||
if self.content:
|
|
||||||
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 not self.check_mode:
|
|
||||||
if self.no_preserve:
|
|
||||||
tar_command = [
|
|
||||||
"tar",
|
|
||||||
"--no-same-permissions",
|
|
||||||
"--no-same-owner",
|
|
||||||
"-xmf",
|
|
||||||
"-",
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
tar_command = ["tar", "-xmf", "-"]
|
|
||||||
|
|
||||||
if dest_file.startswith("/"):
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
with TemporaryFile() as tar_buffer:
|
|
||||||
with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
|
|
||||||
tar.add(src_file, dest_file)
|
|
||||||
tar_buffer.seek(0)
|
|
||||||
commands = []
|
|
||||||
# push command in chunk mode
|
|
||||||
size = 1024 * 1024
|
|
||||||
while True:
|
|
||||||
data = tar_buffer.read(size)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
commands.append(data)
|
|
||||||
|
|
||||||
stderr, stdout = [], []
|
|
||||||
while response.is_open():
|
|
||||||
if response.peek_stdout():
|
|
||||||
stdout.append(response.read_stdout().rstrip("\n"))
|
|
||||||
if response.peek_stderr():
|
|
||||||
stderr.append(response.read_stderr().rstrip("\n"))
|
|
||||||
if commands:
|
|
||||||
cmd = commands.pop(0)
|
|
||||||
response.write_stdin(cmd)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
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.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
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_pod(svc):
|
|
||||||
module = svc.module
|
|
||||||
namespace = module.params.get("namespace")
|
|
||||||
name = module.params.get("pod")
|
|
||||||
container = module.params.get("container")
|
|
||||||
|
|
||||||
try:
|
|
||||||
resource = svc.find_resource("Pod", None, True)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_json(msg=to_native(e))
|
|
||||||
|
|
||||||
def _fail(exc):
|
|
||||||
arg = {}
|
|
||||||
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"]:
|
|
||||||
if hasattr(exc, attr):
|
|
||||||
arg[attr] = getattr(exc, attr)
|
|
||||||
module.fail_json(msg=msg, **arg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = svc.client.get(resource, name=name, namespace=namespace)
|
|
||||||
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
|
|
||||||
except Exception as exc:
|
|
||||||
_fail(exc)
|
|
||||||
@@ -14,9 +14,8 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
class ApplyException(Exception):
|
class ApplyException(Exception):
|
||||||
"""Could not apply patch"""
|
""" Could not apply patch """
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
# Implement ConfigMapHash and SecretHash equivalents
|
# Implement ConfigMapHash and SecretHash equivalents
|
||||||
# Based on https://github.com/kubernetes/kubernetes/pull/49961
|
# Based on https://github.com/kubernetes/kubernetes/pull/49961
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -24,7 +23,6 @@ import hashlib
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import string
|
import string
|
||||||
|
|
||||||
maketrans = string.maketrans
|
maketrans = string.maketrans
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
maketrans = str.maketrans
|
maketrans = str.maketrans
|
||||||
@@ -46,21 +44,14 @@ def sorted_dict(unsorted_dict):
|
|||||||
|
|
||||||
def generate_hash(resource):
|
def generate_hash(resource):
|
||||||
# Get name from metadata
|
# Get name from metadata
|
||||||
metada = resource.get("metadata", {})
|
resource['name'] = resource.get('metadata', {}).get('name', '')
|
||||||
key = "name"
|
if resource['kind'] == 'ConfigMap':
|
||||||
resource["name"] = metada.get("name", "")
|
marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name'])
|
||||||
generate_name = metada.get("generateName", "")
|
del(resource['name'])
|
||||||
if resource["name"] == "" and generate_name:
|
|
||||||
del resource["name"]
|
|
||||||
key = "generateName"
|
|
||||||
resource["generateName"] = generate_name
|
|
||||||
if resource["kind"] == "ConfigMap":
|
|
||||||
marshalled = marshal(sorted_dict(resource), ["data", "kind", key])
|
|
||||||
del resource[key]
|
|
||||||
return encode(marshalled)
|
return encode(marshalled)
|
||||||
if resource["kind"] == "Secret":
|
if resource['kind'] == 'Secret':
|
||||||
marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"])
|
marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name', 'type'])
|
||||||
del resource[key]
|
del(resource['name'])
|
||||||
return encode(marshalled)
|
return encode(marshalled)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -69,10 +60,8 @@ def marshal(data, keys):
|
|||||||
ordered = OrderedDict()
|
ordered = OrderedDict()
|
||||||
for key in keys:
|
for key in keys:
|
||||||
ordered[key] = data.get(key, "")
|
ordered[key] = data.get(key, "")
|
||||||
return json.dumps(ordered, separators=(",", ":")).encode("utf-8")
|
return json.dumps(ordered, separators=(',', ':')).encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def encode(resource):
|
def encode(resource):
|
||||||
return (
|
return hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
||||||
hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -7,31 +7,138 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.six import string_types
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
|
||||||
LooseVersion,
|
|
||||||
)
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
HAS_YAML = True
|
HAS_YAML = True
|
||||||
YAML_IMP_ERR = None
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
YAML_IMP_ERR = traceback.format_exc()
|
YAML_IMP_ERR = traceback.format_exc()
|
||||||
HAS_YAML = False
|
HAS_YAML = False
|
||||||
|
|
||||||
|
|
||||||
def parse_helm_plugin_list(output=None):
|
@contextmanager
|
||||||
|
def prepare_helm_environ_update(module):
|
||||||
|
environ_update = {}
|
||||||
|
file_to_cleam_up = None
|
||||||
|
kubeconfig_path = module.params.get('kubeconfig')
|
||||||
|
if module.params.get('context') is not None:
|
||||||
|
environ_update["HELM_KUBECONTEXT"] = module.params.get('context')
|
||||||
|
if module.params.get('release_namespace'):
|
||||||
|
environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace')
|
||||||
|
if module.params.get("api_key"):
|
||||||
|
environ_update["HELM_KUBETOKEN"] = module.params["api_key"]
|
||||||
|
if module.params.get("host"):
|
||||||
|
environ_update["HELM_KUBEAPISERVER"] = module.params["host"]
|
||||||
|
if module.params.get("validate_certs") is False or module.params.get("ca_cert"):
|
||||||
|
kubeconfig_path = write_temp_kubeconfig(
|
||||||
|
module.params["host"],
|
||||||
|
validate_certs=module.params["validate_certs"],
|
||||||
|
ca_cert=module.params["ca_cert"])
|
||||||
|
file_to_cleam_up = kubeconfig_path
|
||||||
|
if kubeconfig_path is not None:
|
||||||
|
environ_update["KUBECONFIG"] = kubeconfig_path
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield environ_update
|
||||||
|
finally:
|
||||||
|
if file_to_cleam_up:
|
||||||
|
os.remove(file_to_cleam_up)
|
||||||
|
|
||||||
|
|
||||||
|
def run_helm(module, command, fails_on_error=True):
|
||||||
|
if not HAS_YAML:
|
||||||
|
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
||||||
|
|
||||||
|
with prepare_helm_environ_update(module) as environ_update:
|
||||||
|
rc, out, err = module.run_command(command, environ_update=environ_update)
|
||||||
|
if fails_on_error and rc != 0:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||||
|
stdout=out,
|
||||||
|
stderr=err,
|
||||||
|
command=command,
|
||||||
|
)
|
||||||
|
return rc, out, err
|
||||||
|
|
||||||
|
|
||||||
|
def get_values(module, command, release_name):
|
||||||
|
"""
|
||||||
|
Get Values from deployed release
|
||||||
|
"""
|
||||||
|
if not HAS_YAML:
|
||||||
|
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
||||||
|
|
||||||
|
get_command = command + " get values --output=yaml " + release_name
|
||||||
|
|
||||||
|
rc, out, err = run_helm(module, get_command)
|
||||||
|
# Helm 3 return "null" string when no values are set
|
||||||
|
if out.rstrip("\n") == "null":
|
||||||
|
return {}
|
||||||
|
return yaml.safe_load(out)
|
||||||
|
|
||||||
|
|
||||||
|
def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
|
||||||
|
# Workaround until https://github.com/helm/helm/pull/8622 is merged
|
||||||
|
content = {
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Config",
|
||||||
|
"clusters": [
|
||||||
|
{
|
||||||
|
"cluster": {
|
||||||
|
"server": server,
|
||||||
|
},
|
||||||
|
"name": "generated-cluster"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contexts": [
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"cluster": "generated-cluster"
|
||||||
|
},
|
||||||
|
"name": "generated-context"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"current-context": "generated-context"
|
||||||
|
}
|
||||||
|
|
||||||
|
if not validate_certs:
|
||||||
|
content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] = True
|
||||||
|
if ca_cert:
|
||||||
|
content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert
|
||||||
|
|
||||||
|
_fd, file_name = tempfile.mkstemp()
|
||||||
|
with os.fdopen(_fd, 'w') as fp:
|
||||||
|
yaml.dump(content, fp)
|
||||||
|
return file_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_helm_plugin_list(module, helm_bin=None):
|
||||||
|
"""
|
||||||
|
Return `helm plugin list`
|
||||||
|
"""
|
||||||
|
if not helm_bin:
|
||||||
|
return []
|
||||||
|
helm_plugin_list = helm_bin + " list"
|
||||||
|
rc, out, err = run_helm(module, helm_plugin_list)
|
||||||
|
if rc != 0 or (out == '' and err == ''):
|
||||||
|
module.fail_json(
|
||||||
|
msg="Failed to get Helm plugin info",
|
||||||
|
command=helm_plugin_list,
|
||||||
|
stdout=out,
|
||||||
|
stderr=err,
|
||||||
|
rc=rc,
|
||||||
|
)
|
||||||
|
return (rc, out, err)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_helm_plugin_list(module, output=None):
|
||||||
"""
|
"""
|
||||||
Parse `helm plugin list`, return list of plugins
|
Parse `helm plugin list`, return list of plugins
|
||||||
"""
|
"""
|
||||||
@@ -42,262 +149,12 @@ def parse_helm_plugin_list(output=None):
|
|||||||
for line in output:
|
for line in output:
|
||||||
if line.startswith("NAME"):
|
if line.startswith("NAME"):
|
||||||
continue
|
continue
|
||||||
name, version, description = line.split("\t", 3)
|
name, version, description = line.split('\t', 3)
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
version = version.strip()
|
version = version.strip()
|
||||||
description = description.strip()
|
description = description.strip()
|
||||||
if name == "":
|
if name == '':
|
||||||
continue
|
continue
|
||||||
ret.append((name, version, description))
|
ret.append((name, version, description))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None, kubeconfig=None):
|
|
||||||
# Workaround until https://github.com/helm/helm/pull/8622 is merged
|
|
||||||
content = {
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Config",
|
|
||||||
"clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}],
|
|
||||||
"contexts": [
|
|
||||||
{"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
|
|
||||||
],
|
|
||||||
"current-context": "generated-context",
|
|
||||||
}
|
|
||||||
if kubeconfig:
|
|
||||||
content = copy.deepcopy(kubeconfig)
|
|
||||||
|
|
||||||
for cluster in content["clusters"]:
|
|
||||||
if server:
|
|
||||||
cluster["cluster"]["server"] = server
|
|
||||||
if not validate_certs:
|
|
||||||
cluster["cluster"]["insecure-skip-tls-verify"] = True
|
|
||||||
if ca_cert:
|
|
||||||
cluster["cluster"]["certificate-authority"] = ca_cert
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
class AnsibleHelmModule(object):
|
|
||||||
|
|
||||||
"""
|
|
||||||
An Ansible module class for Kubernetes.core helm modules
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
self._module = None
|
|
||||||
if "module" in kwargs:
|
|
||||||
self._module = kwargs.get("module")
|
|
||||||
else:
|
|
||||||
self._module = AnsibleModule(**kwargs)
|
|
||||||
|
|
||||||
self.helm_env = None
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._module, name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def params(self):
|
|
||||||
return self._module.params
|
|
||||||
|
|
||||||
def _prepare_helm_environment(self):
|
|
||||||
param_to_env_mapping = [
|
|
||||||
("context", "HELM_KUBECONTEXT"),
|
|
||||||
("release_namespace", "HELM_NAMESPACE"),
|
|
||||||
("api_key", "HELM_KUBETOKEN"),
|
|
||||||
("host", "HELM_KUBEAPISERVER"),
|
|
||||||
]
|
|
||||||
|
|
||||||
env_update = {}
|
|
||||||
for p, env in param_to_env_mapping:
|
|
||||||
if self.params.get(p):
|
|
||||||
env_update[env] = self.params.get(p)
|
|
||||||
|
|
||||||
kubeconfig_content = None
|
|
||||||
kubeconfig = self.params.get("kubeconfig")
|
|
||||||
if kubeconfig:
|
|
||||||
if isinstance(kubeconfig, string_types):
|
|
||||||
with open(kubeconfig) as fd:
|
|
||||||
kubeconfig_content = yaml.safe_load(fd)
|
|
||||||
elif isinstance(kubeconfig, dict):
|
|
||||||
kubeconfig_content = kubeconfig
|
|
||||||
|
|
||||||
if self.params.get("ca_cert"):
|
|
||||||
ca_cert = self.params.get("ca_cert")
|
|
||||||
if LooseVersion(self.get_helm_version()) < LooseVersion("3.5.0"):
|
|
||||||
# update certs from kubeconfig
|
|
||||||
kubeconfig_content = write_temp_kubeconfig(
|
|
||||||
server=self.params.get("host"),
|
|
||||||
ca_cert=ca_cert,
|
|
||||||
kubeconfig=kubeconfig_content,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
env_update["HELM_KUBECAFILE"] = ca_cert
|
|
||||||
|
|
||||||
if self.params.get("validate_certs") is False:
|
|
||||||
validate_certs = self.params.get("validate_certs")
|
|
||||||
if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
|
|
||||||
# update certs from kubeconfig
|
|
||||||
kubeconfig_content = write_temp_kubeconfig(
|
|
||||||
server=self.params.get("host"),
|
|
||||||
validate_certs=validate_certs,
|
|
||||||
kubeconfig=kubeconfig_content,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
env_update["HELM_KUBEINSECURE_SKIP_TLS_VERIFY"] = "true"
|
|
||||||
|
|
||||||
if kubeconfig_content:
|
|
||||||
fd, kubeconfig_path = tempfile.mkstemp()
|
|
||||||
with os.fdopen(fd, "w") as fp:
|
|
||||||
json.dump(kubeconfig_content, fp)
|
|
||||||
|
|
||||||
env_update["KUBECONFIG"] = kubeconfig_path
|
|
||||||
self.add_cleanup_file(kubeconfig_path)
|
|
||||||
|
|
||||||
return env_update
|
|
||||||
|
|
||||||
@property
|
|
||||||
def env_update(self):
|
|
||||||
if self.helm_env is None:
|
|
||||||
self.helm_env = self._prepare_helm_environment()
|
|
||||||
return self.helm_env
|
|
||||||
|
|
||||||
def run_helm_command(self, command, fails_on_error=True):
|
|
||||||
if not HAS_YAML:
|
|
||||||
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
|
||||||
|
|
||||||
rc, out, err = self.run_command(command, environ_update=self.env_update)
|
|
||||||
if fails_on_error and rc != 0:
|
|
||||||
self.fail_json(
|
|
||||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
|
||||||
rc, out, err
|
|
||||||
),
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
command=command,
|
|
||||||
)
|
|
||||||
return rc, out, err
|
|
||||||
|
|
||||||
def get_helm_binary(self):
|
|
||||||
return self.params.get("binary_path") or self.get_bin_path(
|
|
||||||
"helm", required=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_helm_version(self):
|
|
||||||
|
|
||||||
command = self.get_helm_binary() + " version"
|
|
||||||
rc, out, err = self.run_command(command)
|
|
||||||
m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
|
|
||||||
if m:
|
|
||||||
return m.group(1)
|
|
||||||
m = re.match(r'Client: &version.Version{SemVer:"v([0-9\.]*)", ', out)
|
|
||||||
if m:
|
|
||||||
return m.group(1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_values(self, release_name, get_all=False):
|
|
||||||
"""
|
|
||||||
Get Values from deployed release
|
|
||||||
"""
|
|
||||||
if not HAS_YAML:
|
|
||||||
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
|
||||||
|
|
||||||
get_command = (
|
|
||||||
self.get_helm_binary() + " get values --output=yaml " + release_name
|
|
||||||
)
|
|
||||||
|
|
||||||
if get_all:
|
|
||||||
get_command += " -a"
|
|
||||||
|
|
||||||
rc, out, err = self.run_helm_command(get_command)
|
|
||||||
# Helm 3 return "null" string when no values are set
|
|
||||||
if out.rstrip("\n") == "null":
|
|
||||||
return {}
|
|
||||||
return yaml.safe_load(out)
|
|
||||||
|
|
||||||
def parse_yaml_content(self, content):
|
|
||||||
|
|
||||||
if not HAS_YAML:
|
|
||||||
self.fail_json(msg=missing_required_lib("yaml"), exception=HAS_YAML)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return list(yaml.safe_load_all(content))
|
|
||||||
except (IOError, yaml.YAMLError) as exc:
|
|
||||||
self.fail_json(
|
|
||||||
msg="Error parsing YAML content: {0}".format(exc), raw_data=content
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_manifest(self, release_name):
|
|
||||||
|
|
||||||
command = [
|
|
||||||
self.get_helm_binary(),
|
|
||||||
"get",
|
|
||||||
"manifest",
|
|
||||||
release_name,
|
|
||||||
]
|
|
||||||
rc, out, err = self.run_helm_command(" ".join(command))
|
|
||||||
if rc != 0:
|
|
||||||
self.fail_json(msg=err)
|
|
||||||
return self.parse_yaml_content(out)
|
|
||||||
|
|
||||||
def get_notes(self, release_name):
|
|
||||||
|
|
||||||
command = [
|
|
||||||
self.get_helm_binary(),
|
|
||||||
"get",
|
|
||||||
"notes",
|
|
||||||
release_name,
|
|
||||||
]
|
|
||||||
rc, out, err = self.run_helm_command(" ".join(command))
|
|
||||||
if rc != 0:
|
|
||||||
self.fail_json(msg=err)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def get_hooks(self, release_name):
|
|
||||||
command = [
|
|
||||||
self.get_helm_binary(),
|
|
||||||
"get",
|
|
||||||
"hooks",
|
|
||||||
release_name,
|
|
||||||
]
|
|
||||||
rc, out, err = self.run_helm_command(" ".join(command))
|
|
||||||
if rc != 0:
|
|
||||||
self.fail_json(msg=err)
|
|
||||||
return self.parse_yaml_content(out)
|
|
||||||
|
|
||||||
def get_helm_plugin_list(self):
|
|
||||||
"""
|
|
||||||
Return `helm plugin list`
|
|
||||||
"""
|
|
||||||
helm_plugin_list = self.get_helm_binary() + " plugin list"
|
|
||||||
rc, out, err = self.run_helm_command(helm_plugin_list)
|
|
||||||
if rc != 0 or (out == "" and err == ""):
|
|
||||||
self.fail_json(
|
|
||||||
msg="Failed to get Helm plugin info",
|
|
||||||
command=helm_plugin_list,
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
return (rc, out, err, helm_plugin_list)
|
|
||||||
|
|
||||||
def get_helm_set_values_args(self, set_values):
|
|
||||||
if any(v.get("value_type") == "json" for v in set_values):
|
|
||||||
if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
|
|
||||||
self.fail_json(
|
|
||||||
msg="This module requires helm >= 3.10.0, to use set_values parameter with value type set to 'json'. current version is {0}".format(
|
|
||||||
self.get_helm_version()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
options = []
|
|
||||||
for opt in set_values:
|
|
||||||
value_type = opt.get("value_type", "raw")
|
|
||||||
value = opt.get("value")
|
|
||||||
|
|
||||||
if value_type == "raw":
|
|
||||||
options.append("--set " + value)
|
|
||||||
else:
|
|
||||||
options.append("--set-{0} '{1}'".format(value_type, value))
|
|
||||||
|
|
||||||
return " ".join(options)
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import env_fallback
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
HELM_AUTH_ARG_SPEC = dict(
|
|
||||||
binary_path=dict(type="path"),
|
|
||||||
context=dict(
|
|
||||||
type="str",
|
|
||||||
aliases=["kube_context"],
|
|
||||||
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
|
||||||
),
|
|
||||||
kubeconfig=dict(
|
|
||||||
type="raw",
|
|
||||||
aliases=["kubeconfig_path"],
|
|
||||||
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
|
||||||
),
|
|
||||||
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"]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
HELM_AUTH_MUTUALLY_EXCLUSIVE = [
|
|
||||||
("context", "ca_cert"),
|
|
||||||
("context", "validate_certs"),
|
|
||||||
]
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
# Copyright: (c) 2021, Red Hat | Ansible
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
import os
|
|
||||||
import hashlib
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from ansible.module_utils.six import iteritems, string_types
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
|
||||||
AUTH_ARG_MAP,
|
|
||||||
AUTH_ARG_SPEC,
|
|
||||||
AUTH_PROXY_HEADERS_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
requires as _requires,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils import (
|
|
||||||
k8sdynamicclient,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import (
|
|
||||||
LazyDiscoverer,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import kubernetes
|
|
||||||
from kubernetes.dynamic.exceptions import (
|
|
||||||
ResourceNotFoundError,
|
|
||||||
ResourceNotUniqueError,
|
|
||||||
)
|
|
||||||
from kubernetes.dynamic.resource import Resource
|
|
||||||
except ImportError:
|
|
||||||
# kubernetes import error is handled in module setup
|
|
||||||
# This is defined only for the sake of Ansible's checked import requirement
|
|
||||||
Resource = Any # type: ignore
|
|
||||||
|
|
||||||
try:
|
|
||||||
import urllib3
|
|
||||||
|
|
||||||
urllib3.disable_warnings()
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
_pool = {}
|
|
||||||
|
|
||||||
|
|
||||||
class unique_string(str):
|
|
||||||
_low = None
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return id(self)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self is other
|
|
||||||
|
|
||||||
def lower(self):
|
|
||||||
if self._low is None:
|
|
||||||
lower = str.lower(self)
|
|
||||||
if str.__eq__(lower, self):
|
|
||||||
self._low = self
|
|
||||||
else:
|
|
||||||
self._low = unique_string(lower)
|
|
||||||
return self._low
|
|
||||||
|
|
||||||
|
|
||||||
def _create_auth_spec(module=None, **kwargs) -> Dict:
|
|
||||||
auth: Dict = {}
|
|
||||||
# If authorization variables aren't defined, look for them in environment variables
|
|
||||||
for true_name, arg_name in AUTH_ARG_MAP.items():
|
|
||||||
if module and module.params.get(arg_name) is not None:
|
|
||||||
auth[true_name] = module.params.get(arg_name)
|
|
||||||
elif arg_name in kwargs and kwargs.get(arg_name) is not None:
|
|
||||||
auth[true_name] = kwargs.get(arg_name)
|
|
||||||
elif true_name in kwargs and kwargs.get(true_name) is not None:
|
|
||||||
# Aliases in kwargs
|
|
||||||
auth[true_name] = kwargs.get(true_name)
|
|
||||||
elif arg_name == "proxy_headers":
|
|
||||||
# specific case for 'proxy_headers' which is a dictionary
|
|
||||||
proxy_headers = {}
|
|
||||||
for key in AUTH_PROXY_HEADERS_SPEC.keys():
|
|
||||||
env_value = os.getenv(
|
|
||||||
"K8S_AUTH_PROXY_HEADERS_{0}".format(key.upper()), None
|
|
||||||
)
|
|
||||||
if env_value is not None:
|
|
||||||
if AUTH_PROXY_HEADERS_SPEC[key].get("type") == "bool":
|
|
||||||
env_value = env_value.lower() not in ["0", "false", "no"]
|
|
||||||
proxy_headers[key] = env_value
|
|
||||||
if proxy_headers is not {}:
|
|
||||||
auth[true_name] = proxy_headers
|
|
||||||
else:
|
|
||||||
env_value = os.getenv(
|
|
||||||
"K8S_AUTH_{0}".format(arg_name.upper()), None
|
|
||||||
) or os.getenv("K8S_AUTH_{0}".format(true_name.upper()), None)
|
|
||||||
if env_value is not None:
|
|
||||||
if AUTH_ARG_SPEC[arg_name].get("type") == "bool":
|
|
||||||
env_value = env_value.lower() not in ["0", "false", "no"]
|
|
||||||
auth[true_name] = env_value
|
|
||||||
|
|
||||||
return auth
|
|
||||||
|
|
||||||
|
|
||||||
def _load_config(auth: Dict) -> None:
|
|
||||||
kubeconfig = auth.get("kubeconfig")
|
|
||||||
optional_arg = {
|
|
||||||
"context": auth.get("context"),
|
|
||||||
"persist_config": auth.get("persist_config"),
|
|
||||||
}
|
|
||||||
if kubeconfig:
|
|
||||||
if isinstance(kubeconfig, string_types):
|
|
||||||
kubernetes.config.load_kube_config(config_file=kubeconfig, **optional_arg)
|
|
||||||
elif isinstance(kubeconfig, dict):
|
|
||||||
kubernetes.config.load_kube_config_from_dict(
|
|
||||||
config_dict=kubeconfig, **optional_arg
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
kubernetes.config.load_kube_config(config_file=None, **optional_arg)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_configuration(auth: Dict):
|
|
||||||
def auth_set(*names: list) -> bool:
|
|
||||||
return all(auth.get(name) for name in names)
|
|
||||||
|
|
||||||
if auth_set("host"):
|
|
||||||
# Removing trailing slashes if any from hostname
|
|
||||||
auth["host"] = auth.get("host").rstrip("/")
|
|
||||||
|
|
||||||
if (
|
|
||||||
auth_set("username", "password", "host")
|
|
||||||
or auth_set("api_key", "host")
|
|
||||||
or auth_set("cert_file", "key_file", "host")
|
|
||||||
):
|
|
||||||
# We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
|
|
||||||
pass
|
|
||||||
elif auth_set("kubeconfig") or auth_set("context"):
|
|
||||||
try:
|
|
||||||
_load_config(auth)
|
|
||||||
except Exception as err:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
else:
|
|
||||||
# First try to do incluster config, then kubeconfig
|
|
||||||
try:
|
|
||||||
kubernetes.config.load_incluster_config()
|
|
||||||
except kubernetes.config.ConfigException:
|
|
||||||
try:
|
|
||||||
_load_config(auth)
|
|
||||||
except Exception as err:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
# Override any values in the default configuration with Ansible parameters
|
|
||||||
# As of kubernetes-client v12.0.0, get_default_copy() is required here
|
|
||||||
try:
|
|
||||||
configuration = kubernetes.client.Configuration().get_default_copy()
|
|
||||||
except AttributeError:
|
|
||||||
configuration = kubernetes.client.Configuration()
|
|
||||||
|
|
||||||
for key, value in iteritems(auth):
|
|
||||||
if key in AUTH_ARG_MAP.keys() and value is not None:
|
|
||||||
if key == "api_key":
|
|
||||||
setattr(
|
|
||||||
configuration, key, {"authorization": "Bearer {0}".format(value)}
|
|
||||||
)
|
|
||||||
elif key == "proxy_headers":
|
|
||||||
headers = urllib3.util.make_headers(**value)
|
|
||||||
setattr(configuration, key, headers)
|
|
||||||
else:
|
|
||||||
setattr(configuration, key, value)
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
|
|
||||||
|
|
||||||
def _create_headers(module=None, **kwargs):
|
|
||||||
header_map = {
|
|
||||||
"impersonate_user": "Impersonate-User",
|
|
||||||
"impersonate_groups": "Impersonate-Group",
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
for arg_name, header_name in header_map.items():
|
|
||||||
value = None
|
|
||||||
if module and module.params.get(arg_name) is not None:
|
|
||||||
value = module.params.get(arg_name)
|
|
||||||
elif arg_name in kwargs and kwargs.get(arg_name) is not None:
|
|
||||||
value = kwargs.get(arg_name)
|
|
||||||
else:
|
|
||||||
value = os.getenv("K8S_AUTH_{0}".format(arg_name.upper()), None)
|
|
||||||
if value is not None:
|
|
||||||
if AUTH_ARG_SPEC[arg_name].get("type") == "list":
|
|
||||||
value = [x for x in value.split(",") if x != ""]
|
|
||||||
if value:
|
|
||||||
headers[header_name] = value
|
|
||||||
return headers
|
|
||||||
|
|
||||||
|
|
||||||
def _configuration_digest(configuration, **kwargs) -> str:
|
|
||||||
m = hashlib.sha256()
|
|
||||||
for k in AUTH_ARG_MAP:
|
|
||||||
if not hasattr(configuration, k):
|
|
||||||
v = None
|
|
||||||
else:
|
|
||||||
v = getattr(configuration, k)
|
|
||||||
if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
|
|
||||||
with open(str(v), "r") as fd:
|
|
||||||
content = fd.read()
|
|
||||||
m.update(content.encode())
|
|
||||||
else:
|
|
||||||
m.update(str(v).encode())
|
|
||||||
for k, v in kwargs.items():
|
|
||||||
content = "{0}: {1}".format(k, v)
|
|
||||||
m.update(content.encode())
|
|
||||||
digest = m.hexdigest()
|
|
||||||
|
|
||||||
return digest
|
|
||||||
|
|
||||||
|
|
||||||
def _set_header(client, header, value):
|
|
||||||
if isinstance(value, list):
|
|
||||||
for v in value:
|
|
||||||
client.set_default_header(header_name=unique_string(header), header_value=v)
|
|
||||||
else:
|
|
||||||
client.set_default_header(header_name=header, header_value=value)
|
|
||||||
|
|
||||||
|
|
||||||
def cache(func):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
client = None
|
|
||||||
hashable_kwargs = {}
|
|
||||||
for k, v in kwargs.items():
|
|
||||||
if isinstance(v, list):
|
|
||||||
hashable_kwargs[k] = ",".join(sorted(v))
|
|
||||||
else:
|
|
||||||
hashable_kwargs[k] = v
|
|
||||||
digest = _configuration_digest(*args, **hashable_kwargs)
|
|
||||||
if digest in _pool:
|
|
||||||
client = _pool[digest]
|
|
||||||
else:
|
|
||||||
client = func(*args, **kwargs)
|
|
||||||
_pool[digest] = client
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
@cache
|
|
||||||
def create_api_client(configuration, **headers):
|
|
||||||
client = kubernetes.client.ApiClient(configuration)
|
|
||||||
for header, value in headers.items():
|
|
||||||
_set_header(client, header, value)
|
|
||||||
return k8sdynamicclient.K8SDynamicClient(client, discoverer=LazyDiscoverer)
|
|
||||||
|
|
||||||
|
|
||||||
class K8SClient:
|
|
||||||
"""A Client class for K8S modules.
|
|
||||||
|
|
||||||
This class has the primary purpose to proxy the kubernetes client and resource objects.
|
|
||||||
If there is a need for other methods or attributes to be proxied, they can be added here.
|
|
||||||
"""
|
|
||||||
|
|
||||||
K8S_SERVER_DRY_RUN = "All"
|
|
||||||
|
|
||||||
def __init__(self, configuration, client, dry_run: bool = False) -> None:
|
|
||||||
self.configuration = configuration
|
|
||||||
self.client = client
|
|
||||||
self.dry_run = dry_run
|
|
||||||
|
|
||||||
@property
|
|
||||||
def resources(self) -> List[Any]:
|
|
||||||
return self.client.resources
|
|
||||||
|
|
||||||
def _find_resource_with_prefix(
|
|
||||||
self, prefix: str, kind: str, api_version: str
|
|
||||||
) -> Resource:
|
|
||||||
for attribute in ["kind", "name", "singular_name"]:
|
|
||||||
try:
|
|
||||||
return self.client.resources.get(
|
|
||||||
**{"prefix": prefix, "api_version": api_version, attribute: kind}
|
|
||||||
)
|
|
||||||
except (ResourceNotFoundError, ResourceNotUniqueError):
|
|
||||||
pass
|
|
||||||
return self.client.resources.get(
|
|
||||||
prefix=prefix, api_version=api_version, short_names=[kind]
|
|
||||||
)
|
|
||||||
|
|
||||||
def resource(self, kind: str, api_version: str) -> Resource:
|
|
||||||
"""Fetch a kubernetes client resource.
|
|
||||||
|
|
||||||
This will attempt to find a kubernetes resource trying, in order, kind,
|
|
||||||
name, singular_name and short_names.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if api_version == "v1":
|
|
||||||
return self._find_resource_with_prefix("api", kind, api_version)
|
|
||||||
except ResourceNotFoundError:
|
|
||||||
pass
|
|
||||||
return self._find_resource_with_prefix(None, kind, api_version)
|
|
||||||
|
|
||||||
def _ensure_dry_run(self, params: Dict) -> Dict:
|
|
||||||
if self.dry_run:
|
|
||||||
params["dry_run"] = self.K8S_SERVER_DRY_RUN
|
|
||||||
return params
|
|
||||||
|
|
||||||
def validate(
|
|
||||||
self, resource, version: Optional[str] = None, strict: Optional[bool] = False
|
|
||||||
):
|
|
||||||
return self.client.validate(resource, version, strict)
|
|
||||||
|
|
||||||
def get(self, resource, **params):
|
|
||||||
return resource.get(**params)
|
|
||||||
|
|
||||||
def delete(self, resource, **params):
|
|
||||||
return resource.delete(**self._ensure_dry_run(params))
|
|
||||||
|
|
||||||
def apply(self, resource, definition, namespace, **params):
|
|
||||||
return resource.apply(
|
|
||||||
definition, namespace=namespace, **self._ensure_dry_run(params)
|
|
||||||
)
|
|
||||||
|
|
||||||
def create(self, resource, definition, **params):
|
|
||||||
return resource.create(definition, **self._ensure_dry_run(params))
|
|
||||||
|
|
||||||
def replace(self, resource, definition, **params):
|
|
||||||
return resource.replace(definition, **self._ensure_dry_run(params))
|
|
||||||
|
|
||||||
def patch(self, resource, definition, **params):
|
|
||||||
return resource.patch(definition, **self._ensure_dry_run(params))
|
|
||||||
|
|
||||||
|
|
||||||
def get_api_client(module=None, **kwargs: Optional[Any]) -> K8SClient:
|
|
||||||
auth_spec = _create_auth_spec(module, **kwargs)
|
|
||||||
if module:
|
|
||||||
requires = module.requires
|
|
||||||
else:
|
|
||||||
requires = _requires
|
|
||||||
if isinstance(auth_spec.get("kubeconfig"), dict):
|
|
||||||
requires("kubernetes", "17.17.0", "to use in-memory config")
|
|
||||||
if auth_spec.get("no_proxy"):
|
|
||||||
requires("kubernetes", "19.15.0", "to use the no_proxy feature")
|
|
||||||
|
|
||||||
try:
|
|
||||||
configuration = _create_configuration(auth_spec)
|
|
||||||
headers = _create_headers(module, **kwargs)
|
|
||||||
client = create_api_client(configuration, **headers)
|
|
||||||
except kubernetes.config.ConfigException as e:
|
|
||||||
msg = "Could not create API client: {0}".format(e)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
dry_run = False
|
|
||||||
if module and module.server_side_dry_run:
|
|
||||||
dry_run = True
|
|
||||||
|
|
||||||
k8s_client = K8SClient(
|
|
||||||
configuration=configuration,
|
|
||||||
client=client,
|
|
||||||
dry_run=dry_run,
|
|
||||||
)
|
|
||||||
|
|
||||||
return k8s_client
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
import traceback
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
|
||||||
LooseVersion,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
|
||||||
from ansible.module_utils.common.text.converters import to_text
|
|
||||||
|
|
||||||
|
|
||||||
class AnsibleK8SModule:
|
|
||||||
"""A base module class for K8S modules.
|
|
||||||
|
|
||||||
This class should be used instead of directly using AnsibleModule. If there
|
|
||||||
is a need for other methods or attributes to be proxied, they can be added
|
|
||||||
here.
|
|
||||||
"""
|
|
||||||
|
|
||||||
default_settings = {
|
|
||||||
"check_k8s": True,
|
|
||||||
"check_pyyaml": True,
|
|
||||||
"module_class": AnsibleModule,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
|
||||||
local_settings = {}
|
|
||||||
for key in AnsibleK8SModule.default_settings:
|
|
||||||
try:
|
|
||||||
local_settings[key] = kwargs.pop(key)
|
|
||||||
except KeyError:
|
|
||||||
local_settings[key] = AnsibleK8SModule.default_settings[key]
|
|
||||||
self.settings = local_settings
|
|
||||||
|
|
||||||
self._module = self.settings["module_class"](**kwargs)
|
|
||||||
|
|
||||||
if self.settings["check_k8s"]:
|
|
||||||
self.requires("kubernetes")
|
|
||||||
self.has_at_least("kubernetes", "12.0.0", warn=True)
|
|
||||||
|
|
||||||
if self.settings["check_pyyaml"]:
|
|
||||||
self.requires("pyyaml")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def check_mode(self):
|
|
||||||
return self._module.check_mode
|
|
||||||
|
|
||||||
@property
|
|
||||||
def server_side_dry_run(self):
|
|
||||||
return self.check_mode and self.has_at_least("kubernetes", "18.20.0")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _diff(self):
|
|
||||||
return self._module._diff
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _name(self):
|
|
||||||
return self._module._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def params(self):
|
|
||||||
return self._module.params
|
|
||||||
|
|
||||||
def warn(self, *args, **kwargs):
|
|
||||||
return self._module.warn(*args, **kwargs)
|
|
||||||
|
|
||||||
def deprecate(self, *args, **kwargs):
|
|
||||||
return self._module.deprecate(*args, **kwargs)
|
|
||||||
|
|
||||||
def debug(self, *args, **kwargs):
|
|
||||||
return self._module.debug(*args, **kwargs)
|
|
||||||
|
|
||||||
def exit_json(self, *args, **kwargs):
|
|
||||||
return self._module.exit_json(*args, **kwargs)
|
|
||||||
|
|
||||||
def fail_json(self, *args, **kwargs):
|
|
||||||
return self._module.fail_json(*args, **kwargs)
|
|
||||||
|
|
||||||
def fail_from_exception(self, exception):
|
|
||||||
msg = to_text(exception)
|
|
||||||
tb = "".join(
|
|
||||||
traceback.format_exception(None, exception, exception.__traceback__)
|
|
||||||
)
|
|
||||||
return self.fail_json(msg=msg, exception=tb)
|
|
||||||
|
|
||||||
def has_at_least(
|
|
||||||
self, dependency: str, minimum: Optional[str] = None, warn: bool = False
|
|
||||||
) -> bool:
|
|
||||||
supported = has_at_least(dependency, minimum)
|
|
||||||
if not supported and warn:
|
|
||||||
self.warn(
|
|
||||||
"{0}<{1} is not supported or tested. Some features may not work.".format(
|
|
||||||
dependency, minimum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return supported
|
|
||||||
|
|
||||||
def requires(
|
|
||||||
self,
|
|
||||||
dependency: str,
|
|
||||||
minimum: Optional[str] = None,
|
|
||||||
reason: Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
try:
|
|
||||||
requires(dependency, minimum, reason=reason)
|
|
||||||
except Exception as e:
|
|
||||||
self.fail_json(msg=to_text(e))
|
|
||||||
|
|
||||||
|
|
||||||
def gather_versions() -> dict:
|
|
||||||
versions = {}
|
|
||||||
try:
|
|
||||||
import jsonpatch
|
|
||||||
|
|
||||||
versions["jsonpatch"] = jsonpatch.__version__
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import kubernetes
|
|
||||||
|
|
||||||
versions["kubernetes"] = kubernetes.__version__
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import kubernetes_validate
|
|
||||||
|
|
||||||
versions["kubernetes-validate"] = kubernetes_validate.__version__
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
versions["pyyaml"] = yaml.__version__
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return versions
|
|
||||||
|
|
||||||
|
|
||||||
def has_at_least(dependency: str, minimum: Optional[str] = None) -> bool:
|
|
||||||
"""Check if a specific dependency is present at a minimum version.
|
|
||||||
|
|
||||||
If a minimum version is not specified it will check only that the
|
|
||||||
dependency is present.
|
|
||||||
"""
|
|
||||||
dependencies = gather_versions()
|
|
||||||
current = dependencies.get(dependency)
|
|
||||||
if current is not None:
|
|
||||||
if minimum is None:
|
|
||||||
return True
|
|
||||||
supported = LooseVersion(current) >= LooseVersion(minimum)
|
|
||||||
return supported
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def requires(
|
|
||||||
dependency: str, minimum: Optional[str] = None, reason: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Fail if a specific dependency is not present at a minimum version.
|
|
||||||
|
|
||||||
If a minimum version is not specified it will require only that the
|
|
||||||
dependency is present. This function raises an exception when the
|
|
||||||
dependency is not found at the required version.
|
|
||||||
"""
|
|
||||||
if not has_at_least(dependency, minimum):
|
|
||||||
if minimum is not None:
|
|
||||||
lib = "{0}>={1}".format(dependency, minimum)
|
|
||||||
else:
|
|
||||||
lib = dependency
|
|
||||||
raise Exception(missing_required_lib(lib, reason=reason))
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Copyright: (c) 2021, Red Hat | Ansible
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTimeout(CoreException):
|
|
||||||
def __init__(self, message="", result=None):
|
|
||||||
self.result = result or {}
|
|
||||||
super().__init__(message)
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# Copyright: (c) 2021, Red Hat | Ansible
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
import os
|
|
||||||
from typing import cast, Dict, Iterable, List, Optional, Union
|
|
||||||
|
|
||||||
from ansible.module_utils.six import string_types
|
|
||||||
from ansible.module_utils.urls import Request
|
|
||||||
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceDefinition(dict):
|
|
||||||
"""Representation of a resource definition.
|
|
||||||
|
|
||||||
This is a thin wrapper around a dictionary representation of a resource
|
|
||||||
definition, with a few properties defined for conveniently accessing the
|
|
||||||
commonly used fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def kind(self) -> Optional[str]:
|
|
||||||
return self.get("kind")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_version(self) -> Optional[str]:
|
|
||||||
return self.get("apiVersion")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def namespace(self) -> Optional[str]:
|
|
||||||
metadata = self.get("metadata", {})
|
|
||||||
return metadata.get("namespace")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> Optional[str]:
|
|
||||||
metadata = self.get("metadata", {})
|
|
||||||
return metadata.get("name")
|
|
||||||
|
|
||||||
|
|
||||||
def create_definitions(params: Dict) -> List[ResourceDefinition]:
|
|
||||||
"""Create a list of ResourceDefinitions from module inputs.
|
|
||||||
|
|
||||||
This will take the module's inputs and return a list of ResourceDefintion
|
|
||||||
objects. The resource definitions returned by this function should be as
|
|
||||||
complete a definition as we can create based on the input. Any *List kinds
|
|
||||||
will be removed and replaced by the resources contained in it.
|
|
||||||
"""
|
|
||||||
if params.get("resource_definition"):
|
|
||||||
d = cast(Union[str, List, Dict], params.get("resource_definition"))
|
|
||||||
definitions = from_yaml(d)
|
|
||||||
elif params.get("src"):
|
|
||||||
d = cast(str, params.get("src"))
|
|
||||||
if hasattr(d, "startswith") and d.startswith(("https://", "http://", "ftp://")):
|
|
||||||
data = Request().open("GET", d).read().decode("utf8")
|
|
||||||
definitions = from_yaml(data)
|
|
||||||
else:
|
|
||||||
definitions = from_file(d)
|
|
||||||
else:
|
|
||||||
# We'll create an empty definition and let merge_params set values
|
|
||||||
# from the module parameters.
|
|
||||||
definitions = [{}]
|
|
||||||
|
|
||||||
resource_definitions: List[Dict] = []
|
|
||||||
for definition in definitions:
|
|
||||||
merge_params(definition, params)
|
|
||||||
kind = cast(Optional[str], definition.get("kind"))
|
|
||||||
if kind and kind.endswith("List"):
|
|
||||||
resource_definitions += flatten_list_kind(definition, params)
|
|
||||||
else:
|
|
||||||
resource_definitions.append(definition)
|
|
||||||
return list(map(ResourceDefinition, resource_definitions))
|
|
||||||
|
|
||||||
|
|
||||||
def from_yaml(definition: Union[str, List, Dict]) -> Iterable[Dict]:
|
|
||||||
"""Load resource definitions from a yaml definition."""
|
|
||||||
definitions: List[Dict] = []
|
|
||||||
if isinstance(definition, string_types):
|
|
||||||
definitions += yaml.safe_load_all(definition)
|
|
||||||
elif isinstance(definition, list):
|
|
||||||
for item in definition:
|
|
||||||
if isinstance(item, string_types):
|
|
||||||
definitions += yaml.safe_load_all(item)
|
|
||||||
else:
|
|
||||||
definitions.append(item)
|
|
||||||
else:
|
|
||||||
definition = cast(Dict, definition)
|
|
||||||
definitions.append(definition)
|
|
||||||
return filter(None, definitions)
|
|
||||||
|
|
||||||
|
|
||||||
def from_file(filepath: str) -> Iterable[Dict]:
|
|
||||||
"""Load resource definitions from a path to a yaml file."""
|
|
||||||
path = os.path.normpath(filepath)
|
|
||||||
with open(path, "rb") as f:
|
|
||||||
definitions = list(yaml.safe_load_all(f))
|
|
||||||
return filter(None, definitions)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_params(definition: Dict, params: Dict) -> Dict:
|
|
||||||
"""Merge module parameters with the resource definition.
|
|
||||||
|
|
||||||
Fields in the resource definition take precedence over module parameters.
|
|
||||||
"""
|
|
||||||
definition.setdefault("kind", params.get("kind"))
|
|
||||||
definition.setdefault("apiVersion", params.get("api_version"))
|
|
||||||
metadata = definition.setdefault("metadata", {})
|
|
||||||
# The following should only be set if we have values for them
|
|
||||||
if params.get("namespace"):
|
|
||||||
metadata.setdefault("namespace", params.get("namespace"))
|
|
||||||
if params.get("name"):
|
|
||||||
metadata.setdefault("name", params.get("name"))
|
|
||||||
if params.get("generate_name"):
|
|
||||||
metadata.setdefault("generateName", params.get("generate_name"))
|
|
||||||
return definition
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_list_kind(definition: Dict, params: Dict) -> List[Dict]:
|
|
||||||
"""Replace *List kind with the items it contains.
|
|
||||||
|
|
||||||
This will take a definition for a *List resource and return a list of
|
|
||||||
definitions for the items contained within the List.
|
|
||||||
"""
|
|
||||||
items = []
|
|
||||||
kind = cast(str, definition.get("kind"))[:-4]
|
|
||||||
api_version = definition.get("apiVersion")
|
|
||||||
for item in definition.get("items", []):
|
|
||||||
item.setdefault("kind", kind)
|
|
||||||
item.setdefault("apiVersion", api_version)
|
|
||||||
items.append(merge_params(item, params))
|
|
||||||
return items
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# Copyright: (c) 2021, Red Hat | Ansible
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_native
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
|
|
||||||
create_definitions,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
diff_objects,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
ResourceTimeout,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import exists
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
|
|
||||||
LabelSelectorFilter,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate(client, module, resource):
|
|
||||||
def _prepend_resource_info(resource, msg):
|
|
||||||
return "%s %s: %s" % (resource["kind"], resource["metadata"]["name"], msg)
|
|
||||||
|
|
||||||
module.requires("kubernetes-validate")
|
|
||||||
|
|
||||||
warnings, errors = client.validate(
|
|
||||||
resource,
|
|
||||||
module.params["validate"].get("version"),
|
|
||||||
module.params["validate"].get("strict"),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors and module.params["validate"]["fail_on_error"]:
|
|
||||||
module.fail_json(
|
|
||||||
msg="\n".join([_prepend_resource_info(resource, error) for error in errors])
|
|
||||||
)
|
|
||||||
return [_prepend_resource_info(resource, msg) for msg in warnings + errors]
|
|
||||||
|
|
||||||
|
|
||||||
def run_module(module) -> None:
|
|
||||||
results = []
|
|
||||||
changed = False
|
|
||||||
client = get_api_client(module)
|
|
||||||
svc = K8sService(client, module)
|
|
||||||
try:
|
|
||||||
definitions = create_definitions(module.params)
|
|
||||||
except Exception as e:
|
|
||||||
msg = "Failed to load resource definition: {0}".format(e)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
for definition in definitions:
|
|
||||||
result = {"changed": False, "result": {}}
|
|
||||||
warnings = []
|
|
||||||
|
|
||||||
if module.params.get("validate") is not None:
|
|
||||||
warnings = validate(client, module, definition)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = perform_action(svc, definition, module.params)
|
|
||||||
except Exception as e:
|
|
||||||
try:
|
|
||||||
error = e.result
|
|
||||||
except AttributeError:
|
|
||||||
error = {}
|
|
||||||
try:
|
|
||||||
error["reason"] = e.__cause__.reason
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
error["msg"] = to_native(e)
|
|
||||||
if warnings:
|
|
||||||
error.setdefault("warnings", []).extend(warnings)
|
|
||||||
|
|
||||||
if module.params.get("continue_on_error"):
|
|
||||||
result["error"] = error
|
|
||||||
else:
|
|
||||||
module.fail_json(**error)
|
|
||||||
|
|
||||||
if warnings:
|
|
||||||
result.setdefault("warnings", []).extend(warnings)
|
|
||||||
changed |= result["changed"]
|
|
||||||
results.append(result)
|
|
||||||
|
|
||||||
if len(results) == 1:
|
|
||||||
module.exit_json(**results[0])
|
|
||||||
|
|
||||||
module.exit_json(**{"changed": changed, "result": {"results": results}})
|
|
||||||
|
|
||||||
|
|
||||||
def perform_action(svc, definition: Dict, params: Dict) -> Dict:
|
|
||||||
origin_name = definition["metadata"].get("name")
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
label_selectors = params.get("label_selectors")
|
|
||||||
state = params.get("state", None)
|
|
||||||
kind = definition.get("kind")
|
|
||||||
api_version = definition.get("apiVersion")
|
|
||||||
|
|
||||||
result = {"changed": False, "result": {}}
|
|
||||||
instance = {}
|
|
||||||
|
|
||||||
resource = svc.find_resource(kind, api_version, fail=True)
|
|
||||||
definition["kind"] = resource.kind
|
|
||||||
definition["apiVersion"] = resource.group_version
|
|
||||||
existing = svc.retrieve(resource, definition)
|
|
||||||
|
|
||||||
if state == "absent":
|
|
||||||
if exists(existing) and existing.kind.endswith("List"):
|
|
||||||
instance = []
|
|
||||||
for item in existing.items:
|
|
||||||
r = svc.delete(resource, item, existing)
|
|
||||||
instance.append(r)
|
|
||||||
else:
|
|
||||||
instance = svc.delete(resource, definition, existing)
|
|
||||||
result["method"] = "delete"
|
|
||||||
if exists(existing):
|
|
||||||
result["changed"] = True
|
|
||||||
else:
|
|
||||||
if label_selectors:
|
|
||||||
filter_selector = LabelSelectorFilter(label_selectors)
|
|
||||||
if not filter_selector.isMatching(definition):
|
|
||||||
result["changed"] = False
|
|
||||||
result["msg"] = (
|
|
||||||
"resource 'kind={kind},name={name},namespace={namespace}' "
|
|
||||||
"filtered by label_selectors.".format(
|
|
||||||
kind=kind,
|
|
||||||
name=origin_name,
|
|
||||||
namespace=namespace,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
if params.get("apply"):
|
|
||||||
instance = svc.apply(resource, definition, existing)
|
|
||||||
result["method"] = "apply"
|
|
||||||
elif not existing:
|
|
||||||
if state == "patched":
|
|
||||||
result.setdefault("warnings", []).append(
|
|
||||||
"resource 'kind={kind},name={name}' was not found but will not be "
|
|
||||||
"created as 'state' parameter has been set to '{state}'".format(
|
|
||||||
kind=kind, name=definition["metadata"].get("name"), state=state
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
instance = svc.create(resource, definition)
|
|
||||||
result["method"] = "create"
|
|
||||||
result["changed"] = True
|
|
||||||
elif params.get("force", False):
|
|
||||||
instance = svc.replace(resource, definition, existing)
|
|
||||||
result["method"] = "replace"
|
|
||||||
else:
|
|
||||||
instance = svc.update(resource, definition, existing)
|
|
||||||
result["method"] = "update"
|
|
||||||
|
|
||||||
# If needed, wait and/or create diff
|
|
||||||
success = True
|
|
||||||
|
|
||||||
if result["method"] == "delete":
|
|
||||||
# wait logic is a bit different for delete as `instance` may be a status object
|
|
||||||
if params.get("wait") and not svc.module.check_mode:
|
|
||||||
success, waited, duration = svc.wait(resource, definition)
|
|
||||||
result["duration"] = duration
|
|
||||||
else:
|
|
||||||
if params.get("wait") and not svc.module.check_mode:
|
|
||||||
success, instance, duration = svc.wait(resource, instance)
|
|
||||||
result["duration"] = duration
|
|
||||||
|
|
||||||
if result["method"] not in ("create", "delete"):
|
|
||||||
if existing:
|
|
||||||
existing = existing.to_dict()
|
|
||||||
else:
|
|
||||||
existing = {}
|
|
||||||
match, diffs = diff_objects(existing, instance)
|
|
||||||
if match and diffs:
|
|
||||||
result.setdefault("warnings", []).append(
|
|
||||||
"No meaningful diff was generated, but the API may not be idempotent "
|
|
||||||
"(only metadata.generation or metadata.resourceVersion were changed)"
|
|
||||||
)
|
|
||||||
result["changed"] = not match
|
|
||||||
if svc.module._diff:
|
|
||||||
result["diff"] = diffs
|
|
||||||
|
|
||||||
result["result"] = instance
|
|
||||||
if not success:
|
|
||||||
raise ResourceTimeout(
|
|
||||||
'"{0}" "{1}": Timed out waiting on resource'.format(
|
|
||||||
definition["kind"], origin_name
|
|
||||||
),
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
@@ -1,496 +0,0 @@
|
|||||||
# Copyright: (c) 2021, Red Hat | Ansible
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
|
|
||||||
generate_hash,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
|
|
||||||
Waiter,
|
|
||||||
exists,
|
|
||||||
resource_absent,
|
|
||||||
get_waiter,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
requires,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
|
||||||
|
|
||||||
try:
|
|
||||||
from kubernetes.dynamic.exceptions import (
|
|
||||||
NotFoundError,
|
|
||||||
ResourceNotFoundError,
|
|
||||||
ResourceNotUniqueError,
|
|
||||||
ConflictError,
|
|
||||||
ForbiddenError,
|
|
||||||
MethodNotAllowedError,
|
|
||||||
BadRequestError,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
from kubernetes.dynamic.resource import Resource, ResourceInstance
|
|
||||||
except ImportError:
|
|
||||||
# These are defined only for the sake of Ansible's checked import requirement
|
|
||||||
Resource = Any # type: ignore
|
|
||||||
ResourceInstance = Any # type: ignore
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
|
|
||||||
apply_object,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
|
|
||||||
recursive_diff,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
|
||||||
_encode_stringdata,
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class K8sService:
|
|
||||||
"""A Service class for K8S modules.
|
|
||||||
This class has the primary purpose is to perform work on the cluster (e.g., create, apply, replace, update, delete).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client, module) -> None:
|
|
||||||
self.client = client
|
|
||||||
self.module = module
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _client_side_dry_run(self):
|
|
||||||
return self.module.check_mode and not self.client.dry_run
|
|
||||||
|
|
||||||
def find_resource(
|
|
||||||
self, kind: str, api_version: str, fail: bool = False
|
|
||||||
) -> Optional[Resource]:
|
|
||||||
try:
|
|
||||||
return self.client.resource(kind, api_version)
|
|
||||||
except (ResourceNotFoundError, ResourceNotUniqueError):
|
|
||||||
if fail:
|
|
||||||
raise CoreException(
|
|
||||||
"Failed to find exact match for %s.%s by [kind, name, singularName, shortNames]"
|
|
||||||
% (api_version, kind)
|
|
||||||
)
|
|
||||||
|
|
||||||
def wait(
|
|
||||||
self, resource: Resource, instance: Dict
|
|
||||||
) -> Tuple[bool, Optional[Dict], int]:
|
|
||||||
wait_sleep = self.module.params.get("wait_sleep")
|
|
||||||
wait_timeout = self.module.params.get("wait_timeout")
|
|
||||||
wait_condition = None
|
|
||||||
if self.module.params.get("wait_condition") and self.module.params[
|
|
||||||
"wait_condition"
|
|
||||||
].get("type"):
|
|
||||||
wait_condition = self.module.params["wait_condition"]
|
|
||||||
state = "present"
|
|
||||||
if self.module.params.get("state") == "absent":
|
|
||||||
state = "absent"
|
|
||||||
label_selectors = self.module.params.get("label_selectors")
|
|
||||||
|
|
||||||
waiter = get_waiter(
|
|
||||||
self.client, resource, condition=wait_condition, state=state
|
|
||||||
)
|
|
||||||
return waiter.wait(
|
|
||||||
timeout=wait_timeout,
|
|
||||||
sleep=wait_sleep,
|
|
||||||
name=instance["metadata"].get("name"),
|
|
||||||
namespace=instance["metadata"].get("namespace"),
|
|
||||||
label_selectors=label_selectors,
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_project_request(self, definition: Dict) -> Dict:
|
|
||||||
definition["kind"] = "ProjectRequest"
|
|
||||||
results = {"changed": False, "result": {}}
|
|
||||||
resource = self.find_resource(
|
|
||||||
"ProjectRequest", definition["apiVersion"], fail=True
|
|
||||||
)
|
|
||||||
if not self.module.check_mode:
|
|
||||||
try:
|
|
||||||
k8s_obj = self.client.create(resource, definition)
|
|
||||||
results["result"] = k8s_obj.to_dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to create object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
results["changed"] = True
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def patch_resource(
|
|
||||||
self,
|
|
||||||
resource: Resource,
|
|
||||||
definition: Dict,
|
|
||||||
name: str,
|
|
||||||
namespace: str,
|
|
||||||
merge_type: str = None,
|
|
||||||
) -> Dict:
|
|
||||||
if merge_type == "json":
|
|
||||||
self.module.deprecate(
|
|
||||||
msg="json as a merge_type value is deprecated. Please use the k8s_json_patch module instead.",
|
|
||||||
version="3.0.0",
|
|
||||||
collection_name="kubernetes.core",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
params = dict(name=name, namespace=namespace)
|
|
||||||
if merge_type:
|
|
||||||
params["content_type"] = "application/{0}-patch+json".format(merge_type)
|
|
||||||
return self.client.patch(resource, definition, **params).to_dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to patch object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
def retrieve(self, resource: Resource, definition: Dict) -> ResourceInstance:
|
|
||||||
state = self.module.params.get("state", None)
|
|
||||||
append_hash = self.module.params.get("append_hash", False)
|
|
||||||
name = definition["metadata"].get("name")
|
|
||||||
generate_name = definition["metadata"].get("generateName")
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
label_selectors = self.module.params.get("label_selectors")
|
|
||||||
existing: ResourceInstance = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# ignore append_hash for resources other than ConfigMap and Secret
|
|
||||||
if append_hash and definition["kind"] in ["ConfigMap", "Secret"]:
|
|
||||||
if name:
|
|
||||||
name = "%s-%s" % (name, generate_hash(definition))
|
|
||||||
definition["metadata"]["name"] = name
|
|
||||||
elif generate_name:
|
|
||||||
definition["metadata"]["generateName"] = "%s-%s" % (
|
|
||||||
generate_name,
|
|
||||||
generate_hash(definition),
|
|
||||||
)
|
|
||||||
params = {}
|
|
||||||
if name:
|
|
||||||
params["name"] = name
|
|
||||||
if namespace:
|
|
||||||
params["namespace"] = namespace
|
|
||||||
if label_selectors:
|
|
||||||
params["label_selector"] = ",".join(label_selectors)
|
|
||||||
if "name" in params or "label_selector" in params:
|
|
||||||
existing = self.client.get(resource, **params)
|
|
||||||
except (NotFoundError, MethodNotAllowedError):
|
|
||||||
pass
|
|
||||||
except ForbiddenError as e:
|
|
||||||
if (
|
|
||||||
definition["kind"] in ["Project", "ProjectRequest"]
|
|
||||||
and state != "absent"
|
|
||||||
):
|
|
||||||
return self.create_project_request(definition)
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to retrieve requested object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to retrieve requested object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
return existing
|
|
||||||
|
|
||||||
def find(
|
|
||||||
self,
|
|
||||||
kind: str,
|
|
||||||
api_version: str,
|
|
||||||
name: str = None,
|
|
||||||
namespace: Optional[str] = None,
|
|
||||||
label_selectors: Optional[List[str]] = None,
|
|
||||||
field_selectors: Optional[List[str]] = None,
|
|
||||||
wait: Optional[bool] = False,
|
|
||||||
wait_sleep: Optional[int] = 5,
|
|
||||||
wait_timeout: Optional[int] = 120,
|
|
||||||
state: Optional[str] = "present",
|
|
||||||
condition: Optional[Dict] = None,
|
|
||||||
) -> Dict:
|
|
||||||
resource = self.find_resource(kind, api_version)
|
|
||||||
api_found = bool(resource)
|
|
||||||
if not api_found:
|
|
||||||
return dict(
|
|
||||||
resources=[],
|
|
||||||
msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(
|
|
||||||
api_version, kind
|
|
||||||
),
|
|
||||||
api_found=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not label_selectors:
|
|
||||||
label_selectors = []
|
|
||||||
if not field_selectors:
|
|
||||||
field_selectors = []
|
|
||||||
|
|
||||||
result = {"resources": [], "api_found": True}
|
|
||||||
|
|
||||||
# With a timeout of 0 the waiter will do a single check and return, effectively not waiting.
|
|
||||||
if not wait:
|
|
||||||
wait_timeout = 0
|
|
||||||
|
|
||||||
if state == "present":
|
|
||||||
predicate = exists
|
|
||||||
else:
|
|
||||||
predicate = resource_absent
|
|
||||||
|
|
||||||
waiter = Waiter(self.client, resource, predicate)
|
|
||||||
|
|
||||||
# This is an initial check to get the resource or resources that we then need to wait on individually.
|
|
||||||
try:
|
|
||||||
success, resources, duration = waiter.wait(
|
|
||||||
timeout=wait_timeout,
|
|
||||||
sleep=wait_sleep,
|
|
||||||
name=name,
|
|
||||||
namespace=namespace,
|
|
||||||
label_selectors=label_selectors,
|
|
||||||
field_selectors=field_selectors,
|
|
||||||
)
|
|
||||||
except BadRequestError:
|
|
||||||
return result
|
|
||||||
except CoreException as e:
|
|
||||||
raise e
|
|
||||||
except Exception as e:
|
|
||||||
raise CoreException(
|
|
||||||
"Exception '{0}' raised while trying to get resource using (name={1}, namespace={2}, label_selectors={3}, field_selectors={4})".format(
|
|
||||||
e, name, namespace, label_selectors, field_selectors
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# There is either no result or there is a List resource with no items
|
|
||||||
if (
|
|
||||||
not resources
|
|
||||||
or resources["kind"].endswith("List")
|
|
||||||
and not resources.get("items")
|
|
||||||
):
|
|
||||||
return result
|
|
||||||
|
|
||||||
instances = resources.get("items") or [resources]
|
|
||||||
|
|
||||||
if not wait:
|
|
||||||
result["resources"] = instances
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Now wait for the specified state of any resource instances we have found.
|
|
||||||
waiter = get_waiter(self.client, resource, state=state, condition=condition)
|
|
||||||
for instance in instances:
|
|
||||||
name = instance["metadata"].get("name")
|
|
||||||
namespace = instance["metadata"].get("namespace")
|
|
||||||
success, res, duration = waiter.wait(
|
|
||||||
timeout=wait_timeout,
|
|
||||||
sleep=wait_sleep,
|
|
||||||
name=name,
|
|
||||||
namespace=namespace,
|
|
||||||
)
|
|
||||||
if not success:
|
|
||||||
raise CoreException(
|
|
||||||
"Failed to gather information about %s(s) even"
|
|
||||||
" after waiting for %s seconds" % (res.get("kind"), duration)
|
|
||||||
)
|
|
||||||
result["resources"].append(res)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def create(self, resource: Resource, definition: Dict) -> Dict:
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
name = definition["metadata"].get("name")
|
|
||||||
|
|
||||||
if self._client_side_dry_run:
|
|
||||||
k8s_obj = _encode_stringdata(definition)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
k8s_obj = self.client.create(
|
|
||||||
resource, definition, namespace=namespace
|
|
||||||
).to_dict()
|
|
||||||
except ConflictError:
|
|
||||||
# Some resources, like ProjectRequests, can't be created multiple times,
|
|
||||||
# because the resources that they create don't match their kind
|
|
||||||
# In this case we'll mark it as unchanged and warn the user
|
|
||||||
self.module.warn(
|
|
||||||
"{0} was not found, but creating it returned a 409 Conflict error. This can happen \
|
|
||||||
if the resource you are creating does not directly create a resource of the same kind.".format(
|
|
||||||
name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to create object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
return k8s_obj
|
|
||||||
|
|
||||||
def apply(
|
|
||||||
self,
|
|
||||||
resource: Resource,
|
|
||||||
definition: Dict,
|
|
||||||
existing: Optional[ResourceInstance] = None,
|
|
||||||
) -> Dict:
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
|
|
||||||
server_side_apply = self.module.params.get("server_side_apply")
|
|
||||||
if server_side_apply:
|
|
||||||
requires("kubernetes", "19.15.0", reason="to use server side apply")
|
|
||||||
if self._client_side_dry_run:
|
|
||||||
ignored, patch = apply_object(resource, _encode_stringdata(definition))
|
|
||||||
if existing:
|
|
||||||
k8s_obj = dict_merge(existing.to_dict(), patch)
|
|
||||||
else:
|
|
||||||
k8s_obj = patch
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
params = {}
|
|
||||||
if server_side_apply:
|
|
||||||
params["server_side"] = True
|
|
||||||
params.update(server_side_apply)
|
|
||||||
k8s_obj = self.client.apply(
|
|
||||||
resource, definition, namespace=namespace, **params
|
|
||||||
).to_dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to apply object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
return k8s_obj
|
|
||||||
|
|
||||||
def replace(
|
|
||||||
self,
|
|
||||||
resource: Resource,
|
|
||||||
definition: Dict,
|
|
||||||
existing: ResourceInstance,
|
|
||||||
) -> Dict:
|
|
||||||
append_hash = self.module.params.get("append_hash", False)
|
|
||||||
name = definition["metadata"].get("name")
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
|
|
||||||
if self._client_side_dry_run:
|
|
||||||
k8s_obj = _encode_stringdata(definition)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
k8s_obj = self.client.replace(
|
|
||||||
resource,
|
|
||||||
definition,
|
|
||||||
name=name,
|
|
||||||
namespace=namespace,
|
|
||||||
append_hash=append_hash,
|
|
||||||
).to_dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to replace object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
return k8s_obj
|
|
||||||
|
|
||||||
def update(
|
|
||||||
self, resource: Resource, definition: Dict, existing: ResourceInstance
|
|
||||||
) -> Dict:
|
|
||||||
name = definition["metadata"].get("name")
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
|
|
||||||
if self._client_side_dry_run:
|
|
||||||
k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition))
|
|
||||||
else:
|
|
||||||
exception = None
|
|
||||||
for merge_type in self.module.params.get("merge_type") or [
|
|
||||||
"strategic-merge",
|
|
||||||
"merge",
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
k8s_obj = self.patch_resource(
|
|
||||||
resource,
|
|
||||||
definition,
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
merge_type=merge_type,
|
|
||||||
)
|
|
||||||
exception = None
|
|
||||||
except CoreException as e:
|
|
||||||
exception = e
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
if exception:
|
|
||||||
raise exception
|
|
||||||
return k8s_obj
|
|
||||||
|
|
||||||
def delete(
|
|
||||||
self,
|
|
||||||
resource: Resource,
|
|
||||||
definition: Dict,
|
|
||||||
existing: Optional[ResourceInstance] = None,
|
|
||||||
) -> Dict:
|
|
||||||
delete_options = self.module.params.get("delete_options")
|
|
||||||
label_selectors = self.module.params.get("label_selectors")
|
|
||||||
name = definition["metadata"].get("name")
|
|
||||||
namespace = definition["metadata"].get("namespace")
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
if not exists(existing):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Delete the object
|
|
||||||
if self._client_side_dry_run:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
if name:
|
|
||||||
params["name"] = name
|
|
||||||
|
|
||||||
if namespace:
|
|
||||||
params["namespace"] = namespace
|
|
||||||
|
|
||||||
if label_selectors:
|
|
||||||
params["label_selector"] = ",".join(label_selectors)
|
|
||||||
|
|
||||||
if delete_options:
|
|
||||||
body = {
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "DeleteOptions",
|
|
||||||
}
|
|
||||||
body.update(delete_options)
|
|
||||||
params["body"] = body
|
|
||||||
|
|
||||||
try:
|
|
||||||
k8s_obj = self.client.delete(resource, **params).to_dict()
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Failed to delete object: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
return k8s_obj
|
|
||||||
|
|
||||||
|
|
||||||
def diff_objects(existing: Dict, new: Dict) -> Tuple[bool, Dict]:
|
|
||||||
result = {}
|
|
||||||
diff = recursive_diff(existing, new)
|
|
||||||
if not diff:
|
|
||||||
return True, result
|
|
||||||
|
|
||||||
result["before"] = diff[0]
|
|
||||||
result["after"] = diff[1]
|
|
||||||
|
|
||||||
if list(result["after"].keys()) != ["metadata"] or list(
|
|
||||||
result["before"].keys()
|
|
||||||
) != ["metadata"]:
|
|
||||||
return False, result
|
|
||||||
|
|
||||||
# If only metadata.generation and metadata.resourceVersion changed, ignore it
|
|
||||||
ignored_keys = set(["generation", "resourceVersion"])
|
|
||||||
|
|
||||||
if not set(result["after"]["metadata"].keys()).issubset(ignored_keys):
|
|
||||||
return False, result
|
|
||||||
if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
|
|
||||||
return False, result
|
|
||||||
|
|
||||||
return True, result
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
import time
|
|
||||||
from functools import partial
|
|
||||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
|
||||||
|
|
||||||
from ansible.module_utils.parsing.convert_bool import boolean
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from kubernetes.dynamic.exceptions import NotFoundError
|
|
||||||
from kubernetes.dynamic.resource import Resource, ResourceField, ResourceInstance
|
|
||||||
except ImportError:
|
|
||||||
# These are defined only for the sake of Ansible's checked import requirement
|
|
||||||
Resource = Any # type: ignore
|
|
||||||
ResourceInstance = Any # type: ignore
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib3.exceptions import HTTPError
|
|
||||||
except ImportError:
|
|
||||||
# Handled during module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def deployment_ready(deployment: ResourceInstance) -> bool:
|
|
||||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
|
||||||
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
|
|
||||||
# deployment.status.replicas is None is perfectly ok if desired replicas == 0
|
|
||||||
# Scaling up means that we also need to check that we're not in a
|
|
||||||
# situation where status.replicas == status.availableReplicas
|
|
||||||
# but spec.replicas != status.replicas
|
|
||||||
return bool(
|
|
||||||
deployment.status
|
|
||||||
and deployment.spec.replicas == (deployment.status.replicas or 0)
|
|
||||||
and deployment.status.availableReplicas == deployment.status.replicas
|
|
||||||
and deployment.status.observedGeneration == deployment.metadata.generation
|
|
||||||
and not deployment.status.unavailableReplicas
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pod_ready(pod: ResourceInstance) -> bool:
|
|
||||||
return bool(
|
|
||||||
pod.status
|
|
||||||
and pod.status.containerStatuses is not None
|
|
||||||
and all(container.ready for container in pod.status.containerStatuses)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def daemonset_ready(daemonset: ResourceInstance) -> bool:
|
|
||||||
return bool(
|
|
||||||
daemonset.status
|
|
||||||
and daemonset.status.desiredNumberScheduled is not None
|
|
||||||
and daemonset.status.updatedNumberScheduled
|
|
||||||
== daemonset.status.desiredNumberScheduled
|
|
||||||
and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled
|
|
||||||
and daemonset.status.observedGeneration == daemonset.metadata.generation
|
|
||||||
and not daemonset.status.unavailableReplicas
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def statefulset_ready(statefulset: ResourceInstance) -> bool:
|
|
||||||
# These may be None
|
|
||||||
updated_replicas = statefulset.status.updatedReplicas or 0
|
|
||||||
ready_replicas = statefulset.status.readyReplicas or 0
|
|
||||||
return bool(
|
|
||||||
statefulset.status
|
|
||||||
and statefulset.spec.updateStrategy.type == "RollingUpdate"
|
|
||||||
and statefulset.status.observedGeneration
|
|
||||||
== (statefulset.metadata.generation or 0)
|
|
||||||
and statefulset.status.updateRevision == statefulset.status.currentRevision
|
|
||||||
and updated_replicas == statefulset.spec.replicas
|
|
||||||
and ready_replicas == statefulset.spec.replicas
|
|
||||||
and statefulset.status.replicas == statefulset.spec.replicas
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def custom_condition(condition: Dict, resource: ResourceInstance) -> bool:
|
|
||||||
if not resource.status or not resource.status.conditions:
|
|
||||||
return False
|
|
||||||
matches = [x for x in resource.status.conditions if x.type == condition["type"]]
|
|
||||||
if not matches:
|
|
||||||
return False
|
|
||||||
# There should never be more than one condition of a specific type
|
|
||||||
match: ResourceField = matches[0]
|
|
||||||
if match.status == "Unknown":
|
|
||||||
if match.status == condition["status"]:
|
|
||||||
if "reason" not in condition:
|
|
||||||
return True
|
|
||||||
if condition["reason"]:
|
|
||||||
return match.reason == condition["reason"]
|
|
||||||
return False
|
|
||||||
status = True if match.status == "True" else False
|
|
||||||
if status == boolean(condition["status"], strict=False):
|
|
||||||
if condition.get("reason"):
|
|
||||||
return match.reason == condition["reason"]
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def resource_absent(resource: ResourceInstance) -> bool:
|
|
||||||
return not exists(resource)
|
|
||||||
|
|
||||||
|
|
||||||
def exists(resource: Optional[ResourceInstance]) -> bool:
|
|
||||||
"""Simple predicate to check for existence of a resource.
|
|
||||||
|
|
||||||
While a List type resource technically always exists, this will only return
|
|
||||||
true if the List contains items."""
|
|
||||||
return bool(resource) and not empty_list(resource)
|
|
||||||
|
|
||||||
|
|
||||||
RESOURCE_PREDICATES = {
|
|
||||||
"DaemonSet": daemonset_ready,
|
|
||||||
"Deployment": deployment_ready,
|
|
||||||
"Pod": pod_ready,
|
|
||||||
"StatefulSet": statefulset_ready,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def empty_list(resource: ResourceInstance) -> bool:
|
|
||||||
return resource["kind"].endswith("List") and not resource.get("items")
|
|
||||||
|
|
||||||
|
|
||||||
def clock(total: int, interval: int) -> Iterator[int]:
|
|
||||||
start = time.monotonic()
|
|
||||||
yield 0
|
|
||||||
while (time.monotonic() - start) < total:
|
|
||||||
time.sleep(interval)
|
|
||||||
yield int(time.monotonic() - start)
|
|
||||||
|
|
||||||
|
|
||||||
class Waiter:
|
|
||||||
def __init__(
|
|
||||||
self, client, resource: Resource, predicate: Callable[[ResourceInstance], bool]
|
|
||||||
):
|
|
||||||
self.client = client
|
|
||||||
self.resource = resource
|
|
||||||
self.predicate = predicate
|
|
||||||
|
|
||||||
def wait(
|
|
||||||
self,
|
|
||||||
timeout: int,
|
|
||||||
sleep: int,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
namespace: Optional[str] = None,
|
|
||||||
label_selectors: Optional[List[str]] = None,
|
|
||||||
field_selectors: Optional[List[str]] = None,
|
|
||||||
) -> Tuple[bool, Dict, int]:
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
if name:
|
|
||||||
params["name"] = name
|
|
||||||
|
|
||||||
if namespace:
|
|
||||||
params["namespace"] = namespace
|
|
||||||
|
|
||||||
if label_selectors:
|
|
||||||
params["label_selector"] = ",".join(label_selectors)
|
|
||||||
|
|
||||||
if field_selectors:
|
|
||||||
params["field_selector"] = ",".join(field_selectors)
|
|
||||||
|
|
||||||
instance = {}
|
|
||||||
response = None
|
|
||||||
elapsed = 0
|
|
||||||
for i in clock(timeout, sleep):
|
|
||||||
exception = None
|
|
||||||
elapsed = i
|
|
||||||
try:
|
|
||||||
response = self.client.get(self.resource, **params)
|
|
||||||
except NotFoundError:
|
|
||||||
response = None
|
|
||||||
# Retry connection errors as it may be intermittent network issues
|
|
||||||
except HTTPError as e:
|
|
||||||
exception = e
|
|
||||||
if self.predicate(response):
|
|
||||||
break
|
|
||||||
if exception:
|
|
||||||
msg = (
|
|
||||||
"Exception '{0}' raised while trying to get resource using {1}".format(
|
|
||||||
exception, params
|
|
||||||
)
|
|
||||||
)
|
|
||||||
raise CoreException(msg) from exception
|
|
||||||
if response:
|
|
||||||
instance = response.to_dict()
|
|
||||||
return self.predicate(response), instance, elapsed
|
|
||||||
|
|
||||||
|
|
||||||
class DummyWaiter:
|
|
||||||
"""A no-op waiter that simply returns the item being waited on.
|
|
||||||
|
|
||||||
No API call will be made with this waiter; the function returns
|
|
||||||
immediately. This waiter is useful for waiting on resource instances in
|
|
||||||
check mode, for example.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wait(
|
|
||||||
self,
|
|
||||||
definition: Dict,
|
|
||||||
timeout: int,
|
|
||||||
sleep: int,
|
|
||||||
label_selectors: Optional[List[str]] = None,
|
|
||||||
) -> Tuple[bool, Optional[Dict], int]:
|
|
||||||
return True, definition, 0
|
|
||||||
|
|
||||||
|
|
||||||
# The better solution would be typing.Protocol, but this is only in 3.8+
|
|
||||||
SupportsWait = Union[Waiter, DummyWaiter]
|
|
||||||
|
|
||||||
|
|
||||||
def get_waiter(
|
|
||||||
client,
|
|
||||||
resource: Resource,
|
|
||||||
state: str = "present",
|
|
||||||
condition: Optional[Dict] = None,
|
|
||||||
check_mode: Optional[bool] = False,
|
|
||||||
) -> SupportsWait:
|
|
||||||
"""Create a Waiter object based on the specified resource.
|
|
||||||
|
|
||||||
This is a convenience method for creating a waiter from a resource.
|
|
||||||
Based on the arguments and the kind of resource, an appropriate waiter
|
|
||||||
will be returned. A waiter can also be created directly, of course.
|
|
||||||
"""
|
|
||||||
if check_mode:
|
|
||||||
return DummyWaiter()
|
|
||||||
if state == "present":
|
|
||||||
if condition:
|
|
||||||
predicate: Callable[[ResourceInstance], bool] = partial(
|
|
||||||
custom_condition, condition
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
predicate = RESOURCE_PREDICATES.get(resource.kind, exists)
|
|
||||||
else:
|
|
||||||
predicate = resource_absent
|
|
||||||
return Waiter(client, resource, predicate)
|
|
||||||
@@ -14,37 +14,26 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
from kubernetes.dynamic import DynamicClient
|
from kubernetes.dynamic import DynamicClient
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
|
from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||||
ApplyException,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class K8SDynamicClient(DynamicClient):
|
class K8SDynamicClient(DynamicClient):
|
||||||
def apply(self, resource, body=None, name=None, namespace=None, **kwargs):
|
def apply(self, resource, body=None, name=None, namespace=None):
|
||||||
body = super().serialize_body(body)
|
body = super().serialize_body(body)
|
||||||
body["metadata"] = body.get("metadata", dict())
|
body['metadata'] = body.get('metadata', dict())
|
||||||
name = name or body["metadata"].get("name")
|
name = name or body['metadata'].get('name')
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError(
|
raise ValueError("name is required to apply {0}.{1}".format(resource.group_version, resource.kind))
|
||||||
"name is required to apply {0}.{1}".format(
|
|
||||||
resource.group_version, resource.kind
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if resource.namespaced:
|
if resource.namespaced:
|
||||||
body["metadata"]["namespace"] = super().ensure_namespace(
|
body['metadata']['namespace'] = super().ensure_namespace(resource, namespace, body)
|
||||||
resource, namespace, body
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
return k8s_apply(resource, body, **kwargs)
|
return k8s_apply(resource, body)
|
||||||
except ApplyException as e:
|
except ApplyException as e:
|
||||||
raise ValueError(
|
raise ValueError("Could not apply strategic merge to %s/%s: %s" %
|
||||||
"Could not apply strategic merge to %s/%s: %s"
|
(body['kind'], body['metadata']['name'], e))
|
||||||
% (body["kind"], body["metadata"]["name"], e)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import re
|
|||||||
|
|
||||||
class Selector(object):
|
class Selector(object):
|
||||||
|
|
||||||
equality_based_operators = ("==", "!=", "=")
|
equality_based_operators = ('==', '!=', '=')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self._operator = None
|
self._operator = None
|
||||||
@@ -27,23 +27,18 @@ class Selector(object):
|
|||||||
for op in self.equality_based_operators:
|
for op in self.equality_based_operators:
|
||||||
idx = no_whitespace_data.find(op)
|
idx = no_whitespace_data.find(op)
|
||||||
if idx != -1:
|
if idx != -1:
|
||||||
self._operator = "in" if op == "==" or op == "=" else "notin"
|
self._operator = "in" if op == '==' or op == '=' else "notin"
|
||||||
self._key = no_whitespace_data[0:idx]
|
self._key = no_whitespace_data[0:idx]
|
||||||
# fmt: off
|
|
||||||
self._data = [no_whitespace_data[idx + len(op):]]
|
self._data = [no_whitespace_data[idx + len(op):]]
|
||||||
# fmt: on
|
|
||||||
break
|
break
|
||||||
|
|
||||||
def parse_set_based_requirement(self, data):
|
def parse_set_based_requirement(self, data):
|
||||||
m = re.match(
|
m = re.match(r'( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)', data)
|
||||||
r"( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)",
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
if m:
|
if m:
|
||||||
self._set_based_requirement = True
|
self._set_based_requirement = True
|
||||||
self._key = m.group(2)
|
self._key = m.group(2)
|
||||||
self._operator = m.group(4)
|
self._operator = m.group(4)
|
||||||
self._data = [x.replace(" ", "") for x in m.group(6).split(",") if x != ""]
|
self._data = [x.replace(' ', '') for x in m.group(6).split(',') if x != '']
|
||||||
return True
|
return True
|
||||||
elif all(x not in data for x in self.equality_based_operators):
|
elif all(x not in data for x in self.equality_based_operators):
|
||||||
self._key = data.rstrip(" ").lstrip(" ")
|
self._key = data.rstrip(" ").lstrip(" ")
|
||||||
@@ -59,21 +54,18 @@ class Selector(object):
|
|||||||
elif self._operator == "notin":
|
elif self._operator == "notin":
|
||||||
return self._key not in labels or labels.get(self._key) not in self._data
|
return self._key not in labels or labels.get(self._key) not in self._data
|
||||||
else:
|
else:
|
||||||
return (
|
return self._key not in labels if self._operator == "!" else self._key in labels
|
||||||
self._key not in labels
|
|
||||||
if self._operator == "!"
|
|
||||||
else self._key in labels
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LabelSelectorFilter(object):
|
class LabelSelectorFilter(object):
|
||||||
|
|
||||||
def __init__(self, label_selectors):
|
def __init__(self, label_selectors):
|
||||||
self.selectors = [Selector(data) for data in label_selectors]
|
self.selectors = [Selector(data) for data in label_selectors]
|
||||||
|
|
||||||
def isMatching(self, definition):
|
def isMatching(self, definition):
|
||||||
if "metadata" not in definition or "labels" not in definition["metadata"]:
|
if "metadata" not in definition or "labels" not in definition['metadata']:
|
||||||
return False
|
return False
|
||||||
labels = definition["metadata"]["labels"]
|
labels = definition['metadata']['labels']
|
||||||
if not isinstance(labels, dict):
|
if not isinstance(labels, dict):
|
||||||
return None
|
return None
|
||||||
return all(sel.isMatch(labels) for sel in self.selectors)
|
return all(sel.isMatch(labels) for sel in self.selectors)
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# Copyright: Ansible Project
|
# Copyright: Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: helm
|
module: helm
|
||||||
|
|
||||||
@@ -27,10 +26,6 @@ requirements:
|
|||||||
description:
|
description:
|
||||||
- Install, upgrade, delete packages with the Helm package manager.
|
- Install, upgrade, delete packages with the Helm package manager.
|
||||||
|
|
||||||
notes:
|
|
||||||
- The default idempotency check can fail to report changes when C(release_state) is set to C(present)
|
|
||||||
and C(chart_repo_url) is defined. Install helm diff >= 3.4.1 for better results.
|
|
||||||
|
|
||||||
options:
|
options:
|
||||||
chart_ref:
|
chart_ref:
|
||||||
description:
|
description:
|
||||||
@@ -51,17 +46,6 @@ options:
|
|||||||
- Chart version to install. If this is not specified, the latest version is installed.
|
- Chart version to install. If this is not specified, the latest version is installed.
|
||||||
required: false
|
required: false
|
||||||
type: str
|
type: str
|
||||||
dependency_update:
|
|
||||||
description:
|
|
||||||
- Run standalone C(helm dependency update CHART) before the operation.
|
|
||||||
- Run inline C(--dependency-update) with C(helm install) command. This feature is not supported yet with the C(helm upgrade) command.
|
|
||||||
- So we should consider to use I(dependency_update) options with I(replace) option enabled when specifying I(chart_repo_url).
|
|
||||||
- The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
|
|
||||||
- For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
|
|
||||||
default: false
|
|
||||||
type: bool
|
|
||||||
aliases: [ dep_up ]
|
|
||||||
version_added: "2.4.0"
|
|
||||||
release_name:
|
release_name:
|
||||||
description:
|
description:
|
||||||
- Release name to manage.
|
- Release name to manage.
|
||||||
@@ -102,34 +86,9 @@ options:
|
|||||||
version_added: '1.1.0'
|
version_added: '1.1.0'
|
||||||
update_repo_cache:
|
update_repo_cache:
|
||||||
description:
|
description:
|
||||||
- Run C(helm repo update) before the operation. Can be run as part of the package installation or as a separate step (see Examples).
|
- Run C(helm repo update) before the operation. Can be run as part of the package installation or as a separate step.
|
||||||
default: false
|
default: false
|
||||||
type: bool
|
type: bool
|
||||||
set_values:
|
|
||||||
description:
|
|
||||||
- Values to pass to chart configuration
|
|
||||||
required: false
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
value:
|
|
||||||
description:
|
|
||||||
- Value to pass to chart configuration (e.g phase=prod).
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
value_type:
|
|
||||||
description:
|
|
||||||
- Use C(raw) set individual value.
|
|
||||||
- Use C(string) to force a string for an individual value.
|
|
||||||
- Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
|
|
||||||
- Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
|
|
||||||
default: raw
|
|
||||||
choices:
|
|
||||||
- raw
|
|
||||||
- string
|
|
||||||
- json
|
|
||||||
- file
|
|
||||||
version_added: '2.4.0'
|
|
||||||
|
|
||||||
#Helm options
|
#Helm options
|
||||||
disable_hook:
|
disable_hook:
|
||||||
@@ -149,24 +108,13 @@ options:
|
|||||||
type: bool
|
type: bool
|
||||||
wait:
|
wait:
|
||||||
description:
|
description:
|
||||||
- When I(release_state) is set to C(present), wait until all Pods, PVCs, Services,
|
- Wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
|
||||||
and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
|
|
||||||
- When I(release_state) is set to C(absent), will wait until all the resources are deleted before returning.
|
|
||||||
It will wait for as long as I(wait_timeout). This feature requires helm>=3.7.0. Added in version 2.3.0.
|
|
||||||
default: False
|
default: False
|
||||||
type: bool
|
type: bool
|
||||||
wait_timeout:
|
wait_timeout:
|
||||||
description:
|
description:
|
||||||
- Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).
|
- Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).
|
||||||
- The use of I(wait_timeout) to wait for kubernetes commands to complete has been deprecated and will be removed after 2022-12-01.
|
|
||||||
type: str
|
type: str
|
||||||
timeout:
|
|
||||||
description:
|
|
||||||
- A Go duration (described here I(https://pkg.go.dev/time#ParseDuration)) value to wait for Kubernetes commands to complete. This defaults to 5m0s.
|
|
||||||
- similar to C(wait_timeout) but does not required C(wait) to be activated.
|
|
||||||
- Mutually exclusive with C(wait_timeout).
|
|
||||||
type: str
|
|
||||||
version_added: "2.3.0"
|
|
||||||
atomic:
|
atomic:
|
||||||
description:
|
description:
|
||||||
- If set, the installation process deletes the installation on failure.
|
- If set, the installation process deletes the installation on failure.
|
||||||
@@ -178,11 +126,6 @@ options:
|
|||||||
type: bool
|
type: bool
|
||||||
default: False
|
default: False
|
||||||
version_added: "0.11.1"
|
version_added: "0.11.1"
|
||||||
post_renderer:
|
|
||||||
description:
|
|
||||||
- Path to an executable to be used for post rendering.
|
|
||||||
type: str
|
|
||||||
version_added: "2.4.0"
|
|
||||||
replace:
|
replace:
|
||||||
description:
|
description:
|
||||||
- Reuse the given name, only if that name is a deleted release which remains in the history.
|
- Reuse the given name, only if that name is a deleted release which remains in the history.
|
||||||
@@ -205,9 +148,9 @@ options:
|
|||||||
version_added: "2.2.0"
|
version_added: "2.2.0"
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- kubernetes.core.helm_common_options
|
- kubernetes.core.helm_common_options
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
|
- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
|
||||||
kubernetes.core.helm:
|
kubernetes.core.helm:
|
||||||
name: test
|
name: test
|
||||||
@@ -250,22 +193,6 @@ EXAMPLES = r"""
|
|||||||
state: absent
|
state: absent
|
||||||
wait: true
|
wait: true
|
||||||
|
|
||||||
- name: Separately update the repository cache
|
|
||||||
kubernetes.core.helm:
|
|
||||||
name: dummy
|
|
||||||
namespace: kube-system
|
|
||||||
state: absent
|
|
||||||
update_repo_cache: true
|
|
||||||
|
|
||||||
- name: Deploy Grafana chart using set values on target
|
|
||||||
kubernetes.core.helm:
|
|
||||||
name: test
|
|
||||||
chart_ref: stable/grafana
|
|
||||||
release_namespace: monitoring
|
|
||||||
set_values:
|
|
||||||
- value: phase=prod
|
|
||||||
value_type: string
|
|
||||||
|
|
||||||
# From git
|
# From git
|
||||||
- name: Git clone stable repo on HEAD
|
- name: Git clone stable repo on HEAD
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
@@ -310,7 +237,7 @@ EXAMPLES = r"""
|
|||||||
enabled: True
|
enabled: True
|
||||||
logging:
|
logging:
|
||||||
enabled: True
|
enabled: True
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r"""
|
||||||
status:
|
status:
|
||||||
@@ -367,30 +294,22 @@ command:
|
|||||||
sample: helm upgrade ...
|
sample: helm upgrade ...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
import copy
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
|
||||||
LooseVersion,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
IMP_YAML_ERR = None
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
IMP_YAML = False
|
IMP_YAML = False
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||||
AnsibleHelmModule,
|
run_helm,
|
||||||
parse_helm_plugin_list,
|
get_values,
|
||||||
)
|
get_helm_plugin_list,
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
|
parse_helm_plugin_list
|
||||||
HELM_AUTH_ARG_SPEC,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -401,46 +320,36 @@ def get_release(state, release_name):
|
|||||||
|
|
||||||
if state is not None:
|
if state is not None:
|
||||||
for release in state:
|
for release in state:
|
||||||
if release["name"] == release_name:
|
if release['name'] == release_name:
|
||||||
return release
|
return release
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_release_status(module, release_name):
|
def get_release_status(module, command, release_name):
|
||||||
"""
|
"""
|
||||||
Get Release state from deployed release
|
Get Release state from deployed release
|
||||||
"""
|
"""
|
||||||
|
|
||||||
list_command = (
|
list_command = command + " list --output=yaml --filter " + release_name
|
||||||
module.get_helm_binary() + " list --output=yaml --filter " + release_name
|
|
||||||
)
|
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(list_command)
|
rc, out, err = run_helm(module, list_command)
|
||||||
|
|
||||||
release = get_release(yaml.safe_load(out), release_name)
|
release = get_release(yaml.safe_load(out), release_name)
|
||||||
|
|
||||||
if release is None: # not install
|
if release is None: # not install
|
||||||
return None
|
return None
|
||||||
|
|
||||||
release["values"] = module.get_values(release_name)
|
release['values'] = get_values(module, command, release_name)
|
||||||
|
|
||||||
return release
|
return release
|
||||||
|
|
||||||
|
|
||||||
def run_repo_update(module):
|
def run_repo_update(module, command):
|
||||||
"""
|
"""
|
||||||
Run Repo update
|
Run Repo update
|
||||||
"""
|
"""
|
||||||
repo_update_command = module.get_helm_binary() + " repo update"
|
repo_update_command = command + " repo update"
|
||||||
rc, out, err = module.run_helm_command(repo_update_command)
|
rc, out, err = run_helm(module, repo_update_command)
|
||||||
|
|
||||||
|
|
||||||
def run_dep_update(module, chart_ref):
|
|
||||||
"""
|
|
||||||
Run dependency update
|
|
||||||
"""
|
|
||||||
dep_update = module.get_helm_binary() + " dependency update " + chart_ref
|
|
||||||
rc, out, err = module.run_helm_command(dep_update)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_chart_info(module, command, chart_ref):
|
def fetch_chart_info(module, command, chart_ref):
|
||||||
@@ -449,39 +358,20 @@ def fetch_chart_info(module, command, chart_ref):
|
|||||||
"""
|
"""
|
||||||
inspect_command = command + " show chart " + chart_ref
|
inspect_command = command + " show chart " + chart_ref
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(inspect_command)
|
rc, out, err = run_helm(module, inspect_command)
|
||||||
|
|
||||||
return yaml.safe_load(out)
|
return yaml.safe_load(out)
|
||||||
|
|
||||||
|
|
||||||
def deploy(
|
def deploy(command, release_name, release_values, chart_name, wait,
|
||||||
command,
|
wait_timeout, disable_hook, force, values_files, history_max, atomic=False,
|
||||||
release_name,
|
create_namespace=False, replace=False, skip_crds=False):
|
||||||
release_values,
|
|
||||||
chart_name,
|
|
||||||
wait,
|
|
||||||
wait_timeout,
|
|
||||||
disable_hook,
|
|
||||||
force,
|
|
||||||
values_files,
|
|
||||||
history_max,
|
|
||||||
atomic=False,
|
|
||||||
create_namespace=False,
|
|
||||||
replace=False,
|
|
||||||
post_renderer=None,
|
|
||||||
skip_crds=False,
|
|
||||||
timeout=None,
|
|
||||||
dependency_update=None,
|
|
||||||
set_value_args=None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Install/upgrade/rollback release chart
|
Install/upgrade/rollback release chart
|
||||||
"""
|
"""
|
||||||
if replace:
|
if replace:
|
||||||
# '--replace' is not supported by 'upgrade -i'
|
# '--replace' is not supported by 'upgrade -i'
|
||||||
deploy_command = command + " install"
|
deploy_command = command + " install"
|
||||||
if dependency_update:
|
|
||||||
deploy_command += " --dependency-update"
|
|
||||||
else:
|
else:
|
||||||
deploy_command = command + " upgrade -i" # install/upgrade
|
deploy_command = command + " upgrade -i" # install/upgrade
|
||||||
|
|
||||||
@@ -496,9 +386,6 @@ def deploy(
|
|||||||
if atomic:
|
if atomic:
|
||||||
deploy_command += " --atomic"
|
deploy_command += " --atomic"
|
||||||
|
|
||||||
if timeout:
|
|
||||||
deploy_command += " --timeout " + timeout
|
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
deploy_command += " --force"
|
deploy_command += " --force"
|
||||||
|
|
||||||
@@ -516,28 +403,22 @@ def deploy(
|
|||||||
deploy_command += " --values=" + value_file
|
deploy_command += " --values=" + value_file
|
||||||
|
|
||||||
if release_values != {}:
|
if release_values != {}:
|
||||||
fd, path = tempfile.mkstemp(suffix=".yml")
|
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||||
with open(path, "w") as yaml_file:
|
with open(path, 'w') as yaml_file:
|
||||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||||
deploy_command += " -f=" + path
|
deploy_command += " -f=" + path
|
||||||
|
|
||||||
if post_renderer:
|
|
||||||
deploy_command = " --post-renderer=" + post_renderer
|
|
||||||
|
|
||||||
if skip_crds:
|
if skip_crds:
|
||||||
deploy_command += " --skip-crds"
|
deploy_command += " --skip-crds"
|
||||||
|
|
||||||
if history_max is not None:
|
if history_max is not None:
|
||||||
deploy_command += " --history-max=%s" % str(history_max)
|
deploy_command += " --history-max=%s" % str(history_max)
|
||||||
|
|
||||||
if set_value_args:
|
|
||||||
deploy_command += " " + set_value_args
|
|
||||||
|
|
||||||
deploy_command += " " + release_name + " " + chart_name
|
deploy_command += " " + release_name + " " + chart_name
|
||||||
return deploy_command
|
return deploy_command
|
||||||
|
|
||||||
|
|
||||||
def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
|
def delete(command, release_name, purge, disable_hook):
|
||||||
"""
|
"""
|
||||||
Delete release chart
|
Delete release chart
|
||||||
"""
|
"""
|
||||||
@@ -550,12 +431,6 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
|
|||||||
if disable_hook:
|
if disable_hook:
|
||||||
delete_command += " --no-hooks"
|
delete_command += " --no-hooks"
|
||||||
|
|
||||||
if wait:
|
|
||||||
delete_command += " --wait"
|
|
||||||
|
|
||||||
if wait_timeout is not None:
|
|
||||||
delete_command += " --timeout " + wait_timeout
|
|
||||||
|
|
||||||
delete_command += " " + release_name
|
delete_command += " " + release_name
|
||||||
|
|
||||||
return delete_command
|
return delete_command
|
||||||
@@ -564,7 +439,7 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
|
|||||||
def load_values_files(values_files):
|
def load_values_files(values_files):
|
||||||
values = {}
|
values = {}
|
||||||
for values_file in values_files or []:
|
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)
|
content = yaml.safe_load(fd)
|
||||||
if not isinstance(content, dict):
|
if not isinstance(content, dict):
|
||||||
continue
|
continue
|
||||||
@@ -573,60 +448,50 @@ def load_values_files(values_files):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_version(plugin):
|
def has_plugin(command, plugin):
|
||||||
"""
|
"""
|
||||||
Check if helm plugin is installed and return corresponding version
|
Check if helm plugin is installed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rc, output, err, command = module.get_helm_plugin_list()
|
cmd = command + " plugin"
|
||||||
out = parse_helm_plugin_list(output=output.splitlines())
|
rc, output, err = get_helm_plugin_list(module, helm_bin=cmd)
|
||||||
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
if not out:
|
if not out:
|
||||||
return None
|
return False
|
||||||
|
|
||||||
for line in out:
|
for line in out:
|
||||||
if line[0] == plugin:
|
if line[0] == plugin:
|
||||||
return line[1]
|
return True
|
||||||
return None
|
return False
|
||||||
|
|
||||||
|
|
||||||
def helmdiff_check(
|
def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
||||||
module,
|
values_files=None, chart_version=None, replace=False):
|
||||||
release_name,
|
|
||||||
chart_ref,
|
|
||||||
release_values,
|
|
||||||
values_files=None,
|
|
||||||
chart_version=None,
|
|
||||||
replace=False,
|
|
||||||
chart_repo_url=None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Use helm diff to determine if a release would change by upgrading a chart.
|
Use helm diff to determine if a release would change by upgrading a chart.
|
||||||
"""
|
"""
|
||||||
cmd = module.get_helm_binary() + " diff upgrade"
|
cmd = helm_cmd + " diff upgrade"
|
||||||
cmd += " " + release_name
|
cmd += " " + release_name
|
||||||
cmd += " " + chart_ref
|
cmd += " " + chart_ref
|
||||||
|
|
||||||
if chart_repo_url is not None:
|
|
||||||
cmd += " " + "--repo=" + chart_repo_url
|
|
||||||
if chart_version is not None:
|
if chart_version is not None:
|
||||||
cmd += " " + "--version=" + chart_version
|
cmd += " " + "--version=" + chart_version
|
||||||
if not replace:
|
if not replace:
|
||||||
cmd += " " + "--reset-values"
|
cmd += " " + "--reset-values"
|
||||||
|
|
||||||
if release_values != {}:
|
if release_values != {}:
|
||||||
fd, path = tempfile.mkstemp(suffix=".yml")
|
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||||
with open(path, "w") as yaml_file:
|
with open(path, 'w') as yaml_file:
|
||||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||||
cmd += " -f=" + path
|
cmd += " -f=" + path
|
||||||
module.add_cleanup_file(path)
|
|
||||||
|
|
||||||
if values_files:
|
if values_files:
|
||||||
for values_file in values_files:
|
for values_file in values_files:
|
||||||
cmd += " -f=" + values_file
|
cmd += " -f=" + values_file
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(cmd)
|
rc, out, err = run_helm(module, cmd)
|
||||||
return (len(out.strip()) > 0, out.strip())
|
return len(out.strip()) > 0
|
||||||
|
|
||||||
|
|
||||||
def default_check(release_status, chart_info, values=None, values_files=None):
|
def default_check(release_status, chart_info, values=None, values_files=None):
|
||||||
@@ -634,71 +499,64 @@ 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.
|
Use default check to determine if release would change by upgrading a chart.
|
||||||
"""
|
"""
|
||||||
# the 'appVersion' specification is optional in a chart
|
# the 'appVersion' specification is optional in a chart
|
||||||
chart_app_version = chart_info.get("appVersion", None)
|
chart_app_version = chart_info.get('appVersion', None)
|
||||||
released_app_version = release_status.get("app_version", 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: ""`
|
# 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 (
|
appversion_is_same = (chart_app_version == released_app_version) or (chart_app_version is None and released_app_version == "")
|
||||||
chart_app_version is None and released_app_version == ""
|
|
||||||
)
|
|
||||||
|
|
||||||
if values_files:
|
if values_files:
|
||||||
values_match = release_status["values"] == load_values_files(values_files)
|
values_match = release_status['values'] == load_values_files(values_files)
|
||||||
else:
|
else:
|
||||||
values_match = release_status["values"] == values
|
values_match = release_status['values'] == values
|
||||||
return (
|
return not values_match \
|
||||||
not values_match
|
or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"] \
|
||||||
or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
|
|
||||||
or not appversion_is_same
|
or not appversion_is_same
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def argument_spec():
|
|
||||||
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
|
|
||||||
arg_spec.update(
|
|
||||||
dict(
|
|
||||||
chart_ref=dict(type="path"),
|
|
||||||
chart_repo_url=dict(type="str"),
|
|
||||||
chart_version=dict(type="str"),
|
|
||||||
dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
|
|
||||||
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),
|
|
||||||
disable_hook=dict(type="bool", default=False),
|
|
||||||
force=dict(type="bool", default=False),
|
|
||||||
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),
|
|
||||||
post_renderer=dict(type="str"),
|
|
||||||
replace=dict(type="bool", default=False),
|
|
||||||
skip_crds=dict(type="bool", default=False),
|
|
||||||
history_max=dict(type="int"),
|
|
||||||
set_values=dict(type="list", elements="dict"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return arg_spec
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global module
|
global module
|
||||||
module = AnsibleHelmModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec(),
|
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),
|
||||||
|
|
||||||
|
# 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'),
|
||||||
|
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']))
|
||||||
|
),
|
||||||
required_if=[
|
required_if=[
|
||||||
("release_state", "present", ["release_name", "chart_ref"]),
|
('release_state', 'present', ['release_name', 'chart_ref']),
|
||||||
("release_state", "absent", ["release_name"]),
|
('release_state', 'absent', ['release_name'])
|
||||||
],
|
],
|
||||||
mutually_exclusive=[
|
mutually_exclusive=[
|
||||||
("context", "ca_cert"),
|
("context", "ca_cert"),
|
||||||
|
("kubeconfig", "ca_cert"),
|
||||||
("replace", "history_max"),
|
("replace", "history_max"),
|
||||||
("wait_timeout", "timeout"),
|
|
||||||
],
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
@@ -708,55 +566,46 @@ def main():
|
|||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
chart_ref = module.params.get("chart_ref")
|
bin_path = module.params.get('binary_path')
|
||||||
chart_repo_url = module.params.get("chart_repo_url")
|
chart_ref = module.params.get('chart_ref')
|
||||||
chart_version = module.params.get("chart_version")
|
chart_repo_url = module.params.get('chart_repo_url')
|
||||||
dependency_update = module.params.get("dependency_update")
|
chart_version = module.params.get('chart_version')
|
||||||
release_name = module.params.get("release_name")
|
release_name = module.params.get('release_name')
|
||||||
release_state = module.params.get("release_state")
|
release_state = module.params.get('release_state')
|
||||||
release_values = module.params.get("release_values")
|
release_values = module.params.get('release_values')
|
||||||
values_files = module.params.get("values_files")
|
values_files = module.params.get('values_files')
|
||||||
update_repo_cache = module.params.get("update_repo_cache")
|
update_repo_cache = module.params.get('update_repo_cache')
|
||||||
|
|
||||||
# Helm options
|
# Helm options
|
||||||
disable_hook = module.params.get("disable_hook")
|
disable_hook = module.params.get('disable_hook')
|
||||||
force = module.params.get("force")
|
force = module.params.get('force')
|
||||||
purge = module.params.get("purge")
|
purge = module.params.get('purge')
|
||||||
wait = module.params.get("wait")
|
wait = module.params.get('wait')
|
||||||
wait_timeout = module.params.get("wait_timeout")
|
wait_timeout = module.params.get('wait_timeout')
|
||||||
atomic = module.params.get("atomic")
|
atomic = module.params.get('atomic')
|
||||||
create_namespace = module.params.get("create_namespace")
|
create_namespace = module.params.get('create_namespace')
|
||||||
post_renderer = module.params.get("post_renderer")
|
replace = module.params.get('replace')
|
||||||
replace = module.params.get("replace")
|
skip_crds = module.params.get('skip_crds')
|
||||||
skip_crds = module.params.get("skip_crds")
|
history_max = module.params.get('history_max')
|
||||||
history_max = module.params.get("history_max")
|
|
||||||
timeout = module.params.get("timeout")
|
if bin_path is not None:
|
||||||
set_values = module.params.get("set_values")
|
helm_cmd_common = bin_path
|
||||||
|
else:
|
||||||
|
helm_cmd_common = module.get_bin_path('helm', required=True)
|
||||||
|
|
||||||
if update_repo_cache:
|
if update_repo_cache:
|
||||||
run_repo_update(module)
|
run_repo_update(module, helm_cmd_common)
|
||||||
|
|
||||||
# Get real/deployed release status
|
# Get real/deployed release status
|
||||||
release_status = get_release_status(module, release_name)
|
release_status = get_release_status(module, helm_cmd_common, release_name)
|
||||||
|
|
||||||
helm_cmd = module.get_helm_binary()
|
# keep helm_cmd_common for get_release_status in module_exit_json
|
||||||
opt_result = {}
|
helm_cmd = helm_cmd_common
|
||||||
if release_state == "absent" and release_status is not None:
|
if release_state == "absent" and release_status is not None:
|
||||||
if replace:
|
if replace:
|
||||||
module.fail_json(msg="replace is not applicable when state is absent")
|
module.fail_json(msg="replace is not applicable when state is absent")
|
||||||
|
|
||||||
if wait:
|
helm_cmd = delete(helm_cmd, release_name, purge, disable_hook)
|
||||||
helm_version = module.get_helm_version()
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
wait = False
|
|
||||||
|
|
||||||
helm_cmd = delete(
|
|
||||||
helm_cmd, release_name, purge, disable_hook, wait, wait_timeout
|
|
||||||
)
|
|
||||||
changed = True
|
changed = True
|
||||||
elif release_state == "present":
|
elif release_state == "present":
|
||||||
|
|
||||||
@@ -769,156 +618,67 @@ def main():
|
|||||||
# Fetch chart info to have real version and real name for chart_ref from archive, folder or url
|
# Fetch chart info to have real version and real name for chart_ref from archive, folder or url
|
||||||
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
|
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
|
||||||
|
|
||||||
if dependency_update:
|
|
||||||
if chart_info.get("dependencies"):
|
|
||||||
# Can't use '--dependency-update' with 'helm upgrade' that is the
|
|
||||||
# default chart install method, so if chart_repo_url is defined
|
|
||||||
# we can't use the dependency update command. But, in the near future
|
|
||||||
# we can get rid of this method and use only '--dependency-update'
|
|
||||||
# option. Please see https://github.com/helm/helm/pull/8810
|
|
||||||
if not chart_repo_url and not re.fullmatch(
|
|
||||||
r"^http[s]*://[\w.:/?&=-]+$", chart_ref
|
|
||||||
):
|
|
||||||
run_dep_update(module, chart_ref)
|
|
||||||
|
|
||||||
# To not add --dependency-update option in the deploy function
|
|
||||||
dependency_update = False
|
|
||||||
else:
|
|
||||||
module.warn(
|
|
||||||
"This is a not stable feature with 'chart_repo_url'. Please consider to use dependency update with on-disk charts"
|
|
||||||
)
|
|
||||||
if not replace:
|
|
||||||
msg_fail = (
|
|
||||||
"'--dependency-update' hasn't been supported yet with 'helm upgrade'. "
|
|
||||||
"Please use 'helm install' instead by adding 'replace' option"
|
|
||||||
)
|
|
||||||
module.fail_json(msg=msg_fail)
|
|
||||||
else:
|
|
||||||
module.warn(
|
|
||||||
"There is no dependencies block defined in Chart.yaml. Dependency update will not be performed. "
|
|
||||||
"Please consider add dependencies block or disable dependency_update to remove this warning."
|
|
||||||
)
|
|
||||||
|
|
||||||
if release_status is None: # Not installed
|
if release_status is None: # Not installed
|
||||||
set_value_args = None
|
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||||
if set_values:
|
disable_hook, False, values_files=values_files, atomic=atomic,
|
||||||
set_value_args = module.get_helm_set_values_args(set_values)
|
create_namespace=create_namespace, replace=replace,
|
||||||
|
skip_crds=skip_crds, history_max=history_max)
|
||||||
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,
|
|
||||||
post_renderer=post_renderer,
|
|
||||||
replace=replace,
|
|
||||||
dependency_update=dependency_update,
|
|
||||||
skip_crds=skip_crds,
|
|
||||||
history_max=history_max,
|
|
||||||
timeout=timeout,
|
|
||||||
set_value_args=set_value_args,
|
|
||||||
)
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
helm_diff_version = get_plugin_version("diff")
|
if has_plugin(helm_cmd_common, "diff") and not chart_repo_url:
|
||||||
if helm_diff_version and (
|
would_change = helmdiff_check(module, helm_cmd_common, release_name, chart_ref,
|
||||||
not chart_repo_url
|
release_values, values_files, chart_version, replace)
|
||||||
or (
|
|
||||||
chart_repo_url
|
|
||||||
and LooseVersion(helm_diff_version) >= LooseVersion("3.4.1")
|
|
||||||
)
|
|
||||||
):
|
|
||||||
(would_change, prepared) = helmdiff_check(
|
|
||||||
module,
|
|
||||||
release_name,
|
|
||||||
chart_ref,
|
|
||||||
release_values,
|
|
||||||
values_files,
|
|
||||||
chart_version,
|
|
||||||
replace,
|
|
||||||
chart_repo_url,
|
|
||||||
)
|
|
||||||
if would_change and module._diff:
|
|
||||||
opt_result["diff"] = {"prepared": prepared}
|
|
||||||
else:
|
else:
|
||||||
module.warn(
|
module.warn("The default idempotency check can fail to report changes in certain cases. "
|
||||||
"The default idempotency check can fail to report changes in certain cases. "
|
"Install helm diff for better results.")
|
||||||
"Install helm diff >= 3.4.1 for better results."
|
would_change = default_check(release_status, chart_info, release_values, values_files)
|
||||||
)
|
|
||||||
would_change = default_check(
|
|
||||||
release_status, chart_info, release_values, values_files
|
|
||||||
)
|
|
||||||
|
|
||||||
if force or would_change:
|
if force or would_change:
|
||||||
set_value_args = None
|
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||||
if set_values:
|
disable_hook, force, values_files=values_files, atomic=atomic,
|
||||||
set_value_args = module.get_helm_set_values_args(set_values)
|
create_namespace=create_namespace, replace=replace,
|
||||||
|
skip_crds=skip_crds, history_max=history_max)
|
||||||
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,
|
|
||||||
post_renderer=post_renderer,
|
|
||||||
replace=replace,
|
|
||||||
skip_crds=skip_crds,
|
|
||||||
history_max=history_max,
|
|
||||||
timeout=timeout,
|
|
||||||
dependency_update=dependency_update,
|
|
||||||
set_value_args=set_value_args,
|
|
||||||
)
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
check_status = {"values": {"current": {}, "declared": {}}}
|
check_status = {
|
||||||
|
'values': {
|
||||||
|
"current": {},
|
||||||
|
"declared": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
if release_status:
|
if release_status:
|
||||||
check_status["values"]["current"] = release_status["values"]
|
check_status['values']['current'] = release_status['values']
|
||||||
check_status["values"]["declared"] = release_status
|
check_status['values']['declared'] = release_status
|
||||||
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=changed,
|
changed=changed,
|
||||||
command=helm_cmd,
|
command=helm_cmd,
|
||||||
status=check_status,
|
status=check_status,
|
||||||
stdout="",
|
stdout='',
|
||||||
stderr="",
|
stderr='',
|
||||||
**opt_result,
|
|
||||||
)
|
)
|
||||||
elif not changed:
|
elif not changed:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=False,
|
changed=False,
|
||||||
status=release_status,
|
status=release_status,
|
||||||
stdout="",
|
stdout='',
|
||||||
stderr="",
|
stderr='',
|
||||||
command=helm_cmd,
|
command=helm_cmd,
|
||||||
**opt_result,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(helm_cmd)
|
rc, out, err = run_helm(module, helm_cmd)
|
||||||
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=changed,
|
changed=changed,
|
||||||
stdout=out,
|
stdout=out,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
status=get_release_status(module, release_name),
|
status=get_release_status(module, helm_cmd_common, release_name),
|
||||||
command=helm_cmd,
|
command=helm_cmd,
|
||||||
**opt_result,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# Copyright: (c) 2020, Ansible Project
|
# Copyright: (c) 2020, Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: helm_info
|
module: helm_info
|
||||||
|
|
||||||
@@ -39,47 +38,18 @@ options:
|
|||||||
required: true
|
required: true
|
||||||
type: str
|
type: str
|
||||||
aliases: [ namespace ]
|
aliases: [ namespace ]
|
||||||
release_state:
|
|
||||||
description:
|
|
||||||
- Show releases as per their states.
|
|
||||||
- Default value is C(deployed) and C(failed).
|
|
||||||
- If set to C(all), show all releases without any filter applied.
|
|
||||||
- If set to C(deployed), show deployed releases.
|
|
||||||
- If set to C(failed), show failed releases.
|
|
||||||
- If set to C(pending), show pending releases.
|
|
||||||
- If set to C(superseded), show superseded releases.
|
|
||||||
- If set to C(uninstalled), show uninstalled releases, if C(helm uninstall --keep-history) was used.
|
|
||||||
- If set to C(uninstalling), show releases that are currently being uninstalled.
|
|
||||||
type: list
|
|
||||||
elements: str
|
|
||||||
version_added: "2.3.0"
|
|
||||||
get_all_values:
|
|
||||||
description:
|
|
||||||
- Set to C(True) if you want to get all (computed) values of the release.
|
|
||||||
- When C(False) (default), only user supplied values are returned.
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: bool
|
|
||||||
version_added: "2.4.0"
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- kubernetes.core.helm_common_options
|
- kubernetes.core.helm_common_options
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Gather information of Grafana chart inside monitoring namespace
|
- name: Deploy latest version of Grafana chart inside monitoring namespace
|
||||||
kubernetes.core.helm_info:
|
kubernetes.core.helm_info:
|
||||||
name: test
|
name: test
|
||||||
release_namespace: monitoring
|
release_namespace: monitoring
|
||||||
|
'''
|
||||||
|
|
||||||
- name: Gather information about test-chart with pending state
|
RETURN = r'''
|
||||||
kubernetes.core.helm_info:
|
|
||||||
name: test-chart
|
|
||||||
release_namespace: testenv
|
|
||||||
release_state:
|
|
||||||
- pending
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
status:
|
status:
|
||||||
type: complex
|
type: complex
|
||||||
description: A dictionary of status output
|
description: A dictionary of status output
|
||||||
@@ -117,83 +87,40 @@ status:
|
|||||||
type: str
|
type: str
|
||||||
returned: always
|
returned: always
|
||||||
description: Dict of Values used to deploy
|
description: Dict of Values used to deploy
|
||||||
hooks:
|
'''
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
description: Hooks of the release
|
|
||||||
returned: always
|
|
||||||
version_added: "2.4.0"
|
|
||||||
notes:
|
|
||||||
type: str
|
|
||||||
description: Notes of the release
|
|
||||||
returned: always
|
|
||||||
version_added: "2.4.0"
|
|
||||||
manifest:
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
description: Manifest of the release
|
|
||||||
returned: always
|
|
||||||
version_added: "2.4.0"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import copy
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
IMP_YAML_ERR = None
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
IMP_YAML = False
|
IMP_YAML = False
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm, get_values
|
||||||
AnsibleHelmModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
|
|
||||||
HELM_AUTH_ARG_SPEC,
|
|
||||||
HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Get Release from all deployed releases
|
# Get Release from all deployed releases
|
||||||
def get_release(state, release_name):
|
def get_release(state, release_name):
|
||||||
if state is not None:
|
if state is not None:
|
||||||
for release in state:
|
for release in state:
|
||||||
if release["name"] == release_name:
|
if release['name'] == release_name:
|
||||||
return release
|
return release
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Get Release state from deployed release
|
# Get Release state from deployed release
|
||||||
def get_release_status(module, release_name, release_state, get_all_values=False):
|
def get_release_status(module, command, release_name):
|
||||||
list_command = module.get_helm_binary() + " list --output=yaml"
|
list_command = command + " list --output=yaml --filter " + release_name
|
||||||
|
|
||||||
valid_release_states = [
|
rc, out, err = run_helm(module, list_command)
|
||||||
"all",
|
|
||||||
"deployed",
|
|
||||||
"failed",
|
|
||||||
"pending",
|
|
||||||
"superseded",
|
|
||||||
"uninstalled",
|
|
||||||
"uninstalling",
|
|
||||||
]
|
|
||||||
|
|
||||||
for local_release_state in release_state:
|
|
||||||
if local_release_state in valid_release_states:
|
|
||||||
list_command += " --%s" % local_release_state
|
|
||||||
|
|
||||||
list_command += " --filter " + release_name
|
|
||||||
rc, out, err = module.run_helm_command(list_command)
|
|
||||||
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||||
rc, out, err
|
command=list_command
|
||||||
),
|
|
||||||
command=list_command,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
release = get_release(yaml.safe_load(out), release_name)
|
release = get_release(yaml.safe_load(out), release_name)
|
||||||
@@ -201,46 +128,51 @@ def get_release_status(module, release_name, release_state, get_all_values=False
|
|||||||
if release is None: # not install
|
if release is None: # not install
|
||||||
return None
|
return None
|
||||||
|
|
||||||
release["values"] = module.get_values(release_name, get_all_values)
|
release['values'] = get_values(module, command, release_name)
|
||||||
release["manifest"] = module.get_manifest(release_name)
|
|
||||||
release["notes"] = module.get_notes(release_name)
|
|
||||||
release["hooks"] = module.get_hooks(release_name)
|
|
||||||
|
|
||||||
return release
|
return release
|
||||||
|
|
||||||
|
|
||||||
def argument_spec():
|
|
||||||
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
|
|
||||||
arg_spec.update(
|
|
||||||
dict(
|
|
||||||
release_name=dict(type="str", required=True, aliases=["name"]),
|
|
||||||
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
|
|
||||||
release_state=dict(type="list", default=[], elements="str"),
|
|
||||||
get_all_values=dict(type="bool", required=False, default=False),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return arg_spec
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global module
|
global module
|
||||||
|
|
||||||
module = AnsibleHelmModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec(),
|
argument_spec=dict(
|
||||||
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
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'])),
|
||||||
|
|
||||||
|
# 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']))
|
||||||
|
),
|
||||||
|
mutually_exclusive=[
|
||||||
|
("context", "ca_cert"),
|
||||||
|
("context", "validate_certs"),
|
||||||
|
("kubeconfig", "ca_cert"),
|
||||||
|
("kubeconfig", "validate_certs")
|
||||||
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not IMP_YAML:
|
if not IMP_YAML:
|
||||||
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
||||||
|
|
||||||
release_name = module.params.get("release_name")
|
bin_path = module.params.get('binary_path')
|
||||||
release_state = module.params.get("release_state")
|
release_name = module.params.get('release_name')
|
||||||
get_all_values = module.params.get("get_all_values")
|
|
||||||
|
|
||||||
release_status = get_release_status(
|
if bin_path is not None:
|
||||||
module, release_name, release_state, get_all_values
|
helm_cmd_common = bin_path
|
||||||
)
|
else:
|
||||||
|
helm_cmd_common = module.get_bin_path('helm', required=True)
|
||||||
|
|
||||||
|
release_status = get_release_status(module, helm_cmd_common, release_name)
|
||||||
|
|
||||||
if release_status is not None:
|
if release_status is not None:
|
||||||
module.exit_json(changed=False, status=release_status)
|
module.exit_json(changed=False, status=release_status)
|
||||||
@@ -248,5 +180,5 @@ def main():
|
|||||||
module.exit_json(changed=False)
|
module.exit_json(changed=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: helm_plugin
|
module: helm_plugin
|
||||||
short_description: Manage Helm plugins
|
short_description: Manage Helm plugins
|
||||||
@@ -24,15 +24,14 @@ options:
|
|||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- If C(state=present) the Helm plugin will be installed.
|
- If C(state=present) the Helm plugin will be installed.
|
||||||
- If C(state=latest) the Helm plugin will be updated. Added in version 2.3.0.
|
|
||||||
- If C(state=absent) the Helm plugin will be removed.
|
- If C(state=absent) the Helm plugin will be removed.
|
||||||
choices: [ absent, present, latest ]
|
choices: [ absent, present ]
|
||||||
default: present
|
default: present
|
||||||
type: str
|
type: str
|
||||||
plugin_name:
|
plugin_name:
|
||||||
description:
|
description:
|
||||||
- Name of Helm plugin.
|
- Name of Helm plugin.
|
||||||
- Required only if C(state=absent) or C(state=latest).
|
- Required only if C(state=absent).
|
||||||
type: str
|
type: str
|
||||||
plugin_path:
|
plugin_path:
|
||||||
description:
|
description:
|
||||||
@@ -41,18 +40,11 @@ options:
|
|||||||
machine and not on Ansible controller.
|
machine and not on Ansible controller.
|
||||||
- Required only if C(state=present).
|
- Required only if C(state=present).
|
||||||
type: str
|
type: str
|
||||||
plugin_version:
|
|
||||||
description:
|
|
||||||
- Plugin version to install. If this is not specified, the latest version is installed.
|
|
||||||
- Ignored when C(state=absent) or C(state=latest).
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
version_added: "2.3.0"
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- kubernetes.core.helm_common_options
|
- kubernetes.core.helm_common_options
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Install Helm env plugin
|
- name: Install Helm env plugin
|
||||||
kubernetes.core.helm_plugin:
|
kubernetes.core.helm_plugin:
|
||||||
plugin_path: https://github.com/adamreese/helm-env
|
plugin_path: https://github.com/adamreese/helm-env
|
||||||
@@ -67,20 +59,9 @@ EXAMPLES = r"""
|
|||||||
kubernetes.core.helm_plugin:
|
kubernetes.core.helm_plugin:
|
||||||
plugin_name: env
|
plugin_name: env
|
||||||
state: absent
|
state: absent
|
||||||
|
'''
|
||||||
|
|
||||||
- name: Install Helm plugin with a specific version
|
RETURN = r'''
|
||||||
kubernetes.core.helm_plugin:
|
|
||||||
plugin_version: 2.0.1
|
|
||||||
plugin_path: https://domain/path/to/plugin.tar.gz
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Update Helm plugin
|
|
||||||
kubernetes.core.helm_plugin:
|
|
||||||
plugin_name: secrets
|
|
||||||
state: latest
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
stdout:
|
stdout:
|
||||||
type: str
|
type: str
|
||||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||||
@@ -106,77 +87,67 @@ rc:
|
|||||||
description: Helm plugin command return code
|
description: Helm plugin command return code
|
||||||
returned: always
|
returned: always
|
||||||
sample: 1
|
sample: 1
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||||
AnsibleHelmModule,
|
run_helm,
|
||||||
parse_helm_plugin_list,
|
get_helm_plugin_list,
|
||||||
|
parse_helm_plugin_list
|
||||||
)
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
|
|
||||||
HELM_AUTH_ARG_SPEC,
|
|
||||||
HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def argument_spec():
|
|
||||||
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
|
|
||||||
arg_spec.update(
|
|
||||||
dict(
|
|
||||||
plugin_path=dict(
|
|
||||||
type="str",
|
|
||||||
),
|
|
||||||
plugin_name=dict(
|
|
||||||
type="str",
|
|
||||||
),
|
|
||||||
plugin_version=dict(
|
|
||||||
type="str",
|
|
||||||
),
|
|
||||||
state=dict(
|
|
||||||
type="str",
|
|
||||||
default="present",
|
|
||||||
choices=["present", "absent", "latest"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return arg_spec
|
|
||||||
|
|
||||||
|
|
||||||
def mutually_exclusive():
|
|
||||||
mutually_ex = copy.deepcopy(HELM_AUTH_MUTUALLY_EXCLUSIVE)
|
|
||||||
mutually_ex.append(("plugin_name", "plugin_path"))
|
|
||||||
return mutually_ex
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleHelmModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec(),
|
argument_spec=dict(
|
||||||
|
binary_path=dict(type='path'),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
plugin_path=dict(type='str',),
|
||||||
|
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'])),
|
||||||
|
|
||||||
|
# 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']))
|
||||||
|
),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
required_if=[
|
required_if=[
|
||||||
("state", "present", ("plugin_path",)),
|
("state", "present", ("plugin_path",)),
|
||||||
("state", "absent", ("plugin_name",)),
|
("state", "absent", ("plugin_name",)),
|
||||||
("state", "latest", ("plugin_name",)),
|
|
||||||
],
|
],
|
||||||
mutually_exclusive=mutually_exclusive(),
|
mutually_exclusive=[
|
||||||
|
('plugin_name', 'plugin_path'),
|
||||||
|
("context", "ca_cert"),
|
||||||
|
("context", "validate_certs"),
|
||||||
|
("kubeconfig", "ca_cert"),
|
||||||
|
("kubeconfig", "validate_certs")
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
state = module.params.get("state")
|
bin_path = module.params.get('binary_path')
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
helm_cmd_common = module.get_helm_binary() + " plugin"
|
if bin_path is not None:
|
||||||
|
helm_cmd_common = bin_path
|
||||||
|
else:
|
||||||
|
helm_cmd_common = 'helm'
|
||||||
|
|
||||||
if state == "present":
|
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||||
helm_cmd_common += " install %s" % module.params.get("plugin_path")
|
|
||||||
plugin_version = module.params.get("plugin_version")
|
helm_cmd_common += " plugin"
|
||||||
if plugin_version is not None:
|
|
||||||
helm_cmd_common += " --version=%s" % plugin_version
|
if state == 'present':
|
||||||
|
helm_cmd_common += " install %s" % module.params.get('plugin_path')
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
rc, out, err = module.run_helm_command(
|
rc, out, err = run_helm(module, helm_cmd_common, fails_on_error=False)
|
||||||
helm_cmd_common, fails_on_error=False
|
|
||||||
)
|
|
||||||
else:
|
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(
|
module.exit_json(
|
||||||
failed=False,
|
failed=False,
|
||||||
changed=False,
|
changed=False,
|
||||||
@@ -184,7 +155,7 @@ def main():
|
|||||||
command=helm_cmd_common,
|
command=helm_cmd_common,
|
||||||
stdout=out,
|
stdout=out,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc
|
||||||
)
|
)
|
||||||
elif rc == 0:
|
elif rc == 0:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
@@ -204,20 +175,20 @@ def main():
|
|||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc,
|
||||||
)
|
)
|
||||||
elif state == "absent":
|
elif state == 'absent':
|
||||||
plugin_name = module.params.get("plugin_name")
|
plugin_name = module.params.get('plugin_name')
|
||||||
rc, output, err, command = module.get_helm_plugin_list()
|
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||||
out = parse_helm_plugin_list(output=output.splitlines())
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
if not out:
|
if not out:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
failed=False,
|
failed=False,
|
||||||
changed=False,
|
changed=False,
|
||||||
msg="Plugin not found or is already uninstalled",
|
msg="Plugin not found or is already uninstalled",
|
||||||
command=command,
|
command=helm_cmd_common + " list",
|
||||||
stdout=output,
|
stdout=output,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc
|
||||||
)
|
)
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
@@ -230,19 +201,17 @@ def main():
|
|||||||
failed=False,
|
failed=False,
|
||||||
changed=False,
|
changed=False,
|
||||||
msg="Plugin not found or is already uninstalled",
|
msg="Plugin not found or is already uninstalled",
|
||||||
command=command,
|
command=helm_cmd_common + " list",
|
||||||
stdout=output,
|
stdout=output,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc
|
||||||
)
|
)
|
||||||
|
|
||||||
helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
|
helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
rc, out, err = module.run_helm_command(
|
rc, out, err = run_helm(module, helm_uninstall_cmd, fails_on_error=False)
|
||||||
helm_uninstall_cmd, fails_on_error=False
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
rc, out, err = (0, "", "")
|
rc, out, err = (0, '', '')
|
||||||
|
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
@@ -251,7 +220,7 @@ def main():
|
|||||||
command=helm_uninstall_cmd,
|
command=helm_uninstall_cmd,
|
||||||
stdout=out,
|
stdout=out,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc
|
||||||
)
|
)
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failed to get Helm plugin uninstall",
|
msg="Failed to get Helm plugin uninstall",
|
||||||
@@ -260,63 +229,7 @@ def main():
|
|||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc,
|
||||||
)
|
)
|
||||||
elif state == "latest":
|
|
||||||
plugin_name = module.params.get("plugin_name")
|
|
||||||
rc, output, err, command = module.get_helm_plugin_list()
|
|
||||||
out = parse_helm_plugin_list(output=output.splitlines())
|
|
||||||
|
|
||||||
if not out:
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
changed=False,
|
|
||||||
msg="Plugin not found",
|
|
||||||
command=command,
|
|
||||||
stdout=output,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
|
|
||||||
found = False
|
|
||||||
for line in out:
|
|
||||||
if line[0] == plugin_name:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
changed=False,
|
|
||||||
msg="Plugin not found",
|
|
||||||
command=command,
|
|
||||||
stdout=output,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
|
|
||||||
helm_update_cmd = "%s update %s" % (helm_cmd_common, plugin_name)
|
|
||||||
if not module.check_mode:
|
|
||||||
rc, out, err = module.run_helm_command(
|
|
||||||
helm_update_cmd, fails_on_error=False
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rc, out, err = (0, "", "")
|
|
||||||
|
|
||||||
if rc == 0:
|
|
||||||
module.exit_json(
|
|
||||||
changed=True,
|
|
||||||
msg="Plugin updated successfully",
|
|
||||||
command=helm_update_cmd,
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
module.fail_json(
|
|
||||||
msg="Failed to get Helm plugin update",
|
|
||||||
command=helm_update_cmd,
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: helm_plugin_info
|
module: helm_plugin_info
|
||||||
short_description: Gather information about Helm plugins
|
short_description: Gather information about Helm plugins
|
||||||
@@ -27,18 +27,18 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- kubernetes.core.helm_common_options
|
- kubernetes.core.helm_common_options
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Gather Helm plugin info
|
- name: Gather Helm plugin info
|
||||||
kubernetes.core.helm_plugin_info:
|
kubernetes.core.helm_plugin_info:
|
||||||
|
|
||||||
- name: Gather Helm env plugin info
|
- name: Gather Helm env plugin info
|
||||||
kubernetes.core.helm_plugin_info:
|
kubernetes.core.helm_plugin_info:
|
||||||
plugin_name: env
|
plugin_name: env
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
stdout:
|
stdout:
|
||||||
type: str
|
type: str
|
||||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||||
@@ -68,60 +68,82 @@ rc:
|
|||||||
description: Helm plugin command return code
|
description: Helm plugin command return code
|
||||||
returned: always
|
returned: always
|
||||||
sample: 1
|
sample: 1
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||||
|
get_helm_plugin_list,
|
||||||
parse_helm_plugin_list,
|
parse_helm_plugin_list,
|
||||||
AnsibleHelmModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
|
|
||||||
HELM_AUTH_ARG_SPEC,
|
|
||||||
HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
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'])),
|
||||||
|
|
||||||
argument_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
|
# Generic auth key
|
||||||
argument_spec.update(
|
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||||
dict(
|
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
|
||||||
plugin_name=dict(
|
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
||||||
type="str",
|
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
||||||
),
|
),
|
||||||
)
|
mutually_exclusive=[
|
||||||
)
|
("context", "ca_cert"),
|
||||||
|
("context", "validate_certs"),
|
||||||
module = AnsibleHelmModule(
|
("kubeconfig", "ca_cert"),
|
||||||
argument_spec=argument_spec,
|
("kubeconfig", "validate_certs")
|
||||||
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
plugin_name = module.params.get("plugin_name")
|
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 = module.get_bin_path(helm_cmd_common, required=True)
|
||||||
|
|
||||||
|
helm_cmd_common += " plugin"
|
||||||
|
|
||||||
|
plugin_name = module.params.get('plugin_name')
|
||||||
|
|
||||||
plugin_list = []
|
plugin_list = []
|
||||||
|
|
||||||
rc, output, err, command = module.get_helm_plugin_list()
|
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||||
|
|
||||||
out = parse_helm_plugin_list(output=output.splitlines())
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
for line in out:
|
for line in out:
|
||||||
if plugin_name is None:
|
if plugin_name is None:
|
||||||
plugin_list.append(
|
plugin_list.append(
|
||||||
{"name": line[0], "version": line[1], "description": line[2]}
|
{
|
||||||
|
"name": line[0],
|
||||||
|
"version": line[1],
|
||||||
|
"description": line[2],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if plugin_name == line[0]:
|
if plugin_name == line[0]:
|
||||||
plugin_list.append(
|
plugin_list.append(
|
||||||
{"name": line[0], "version": line[1], "description": line[2]}
|
{
|
||||||
|
"name": line[0],
|
||||||
|
"version": line[1],
|
||||||
|
"description": line[2],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True,
|
||||||
command=command,
|
command=helm_cmd_common + " list",
|
||||||
stdout=output,
|
stdout=output,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc,
|
||||||
@@ -129,5 +151,5 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2022, 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"""
|
|
||||||
---
|
|
||||||
module: helm_pull
|
|
||||||
short_description: download a chart from a repository and (optionally) unpack it in local directory.
|
|
||||||
version_added: "2.4.0"
|
|
||||||
author:
|
|
||||||
- Aubin Bikouo (@abikouo)
|
|
||||||
description:
|
|
||||||
- Retrieve a package from a package repository, and download it locally.
|
|
||||||
- It can also be used to perform cryptographic verification of a chart without installing the chart.
|
|
||||||
- There are options for unpacking the chart after download.
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- "helm >= 3.0 (https://github.com/helm/helm/releases)"
|
|
||||||
|
|
||||||
options:
|
|
||||||
chart_ref:
|
|
||||||
description:
|
|
||||||
- chart name on chart repository.
|
|
||||||
- absolute URL.
|
|
||||||
required: true
|
|
||||||
type: str
|
|
||||||
chart_version:
|
|
||||||
description:
|
|
||||||
- Specify a version constraint for the chart version to use.
|
|
||||||
- This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).
|
|
||||||
- Mutually exclusive with C(chart_devel).
|
|
||||||
type: str
|
|
||||||
verify_chart:
|
|
||||||
description:
|
|
||||||
- Verify the package before using it.
|
|
||||||
default: False
|
|
||||||
type: bool
|
|
||||||
verify_chart_keyring:
|
|
||||||
description:
|
|
||||||
- location of public keys used for verification.
|
|
||||||
type: path
|
|
||||||
provenance:
|
|
||||||
description:
|
|
||||||
- Fetch the provenance file, but don't perform verification.
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
repo_url:
|
|
||||||
description:
|
|
||||||
- chart repository url where to locate the requested chart.
|
|
||||||
type: str
|
|
||||||
aliases: [ url, chart_repo_url ]
|
|
||||||
repo_username:
|
|
||||||
description:
|
|
||||||
- Chart repository username where to locate the requested chart.
|
|
||||||
- Required if C(repo_password) is specified.
|
|
||||||
type: str
|
|
||||||
aliases: [ username, chart_repo_username ]
|
|
||||||
repo_password:
|
|
||||||
description:
|
|
||||||
- Chart repository password where to locate the requested chart.
|
|
||||||
- Required if C(repo_username) is specified.
|
|
||||||
type: str
|
|
||||||
aliases: [ password, chart_repo_password ]
|
|
||||||
pass_credentials:
|
|
||||||
description:
|
|
||||||
- Pass credentials to all domains.
|
|
||||||
default: False
|
|
||||||
type: bool
|
|
||||||
skip_tls_certs_check:
|
|
||||||
description:
|
|
||||||
- Whether or not to check tls certificate for the chart download.
|
|
||||||
- Requires helm >= 3.3.0.
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
chart_devel:
|
|
||||||
description:
|
|
||||||
- Use development versions, too. Equivalent to version '>0.0.0-0'.
|
|
||||||
- Mutually exclusive with C(chart_version).
|
|
||||||
type: bool
|
|
||||||
untar_chart:
|
|
||||||
description:
|
|
||||||
- if set to true, will untar the chart after downloading it.
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
destination:
|
|
||||||
description:
|
|
||||||
- location to write the chart.
|
|
||||||
type: path
|
|
||||||
required: True
|
|
||||||
chart_ca_cert:
|
|
||||||
description:
|
|
||||||
- Verify certificates of HTTPS-enabled servers using this CA bundle.
|
|
||||||
- Requires helm >= 3.1.0.
|
|
||||||
type: path
|
|
||||||
chart_ssl_cert_file:
|
|
||||||
description:
|
|
||||||
- Identify HTTPS client using this SSL certificate file.
|
|
||||||
- Requires helm >= 3.1.0.
|
|
||||||
type: path
|
|
||||||
chart_ssl_key_file:
|
|
||||||
description:
|
|
||||||
- Identify HTTPS client using this SSL key file
|
|
||||||
- Requires helm >= 3.1.0.
|
|
||||||
type: path
|
|
||||||
binary_path:
|
|
||||||
description:
|
|
||||||
- The path of a helm binary to use.
|
|
||||||
required: false
|
|
||||||
type: path
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = r"""
|
|
||||||
- name: Download chart using chart url
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
|
|
||||||
destination: /path/to/chart
|
|
||||||
|
|
||||||
- name: Download Chart using chart_name and repo_url
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
|
|
||||||
- name: Download Chart (skip tls certificate check)
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
skip_tls_certs_check: yes
|
|
||||||
|
|
||||||
- name: Download Chart using chart registry credentials
|
|
||||||
kubernetes.core.helm_pull:
|
|
||||||
chart_ref: redis
|
|
||||||
repo_url: https://charts.bitnami.com/bitnami
|
|
||||||
untar_chart: yes
|
|
||||||
destination: /path/to/chart
|
|
||||||
username: myuser
|
|
||||||
password: mypassword123
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
stdout:
|
|
||||||
type: str
|
|
||||||
description: Full `helm pull` command stdout, in case you want to display it or examine the event log
|
|
||||||
returned: always
|
|
||||||
sample: ''
|
|
||||||
stderr:
|
|
||||||
type: str
|
|
||||||
description: Full `helm pull` command stderr, in case you want to display it or examine the event log
|
|
||||||
returned: always
|
|
||||||
sample: ''
|
|
||||||
command:
|
|
||||||
type: str
|
|
||||||
description: Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.
|
|
||||||
returned: always
|
|
||||||
sample: helm pull --repo test ...
|
|
||||||
rc:
|
|
||||||
type: int
|
|
||||||
description: Helm pull command return code
|
|
||||||
returned: always
|
|
||||||
sample: 1
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
|
||||||
AnsibleHelmModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
|
||||||
LooseVersion,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argspec = dict(
|
|
||||||
chart_ref=dict(type="str", required=True),
|
|
||||||
chart_version=dict(type="str"),
|
|
||||||
verify_chart=dict(type="bool", default=False),
|
|
||||||
verify_chart_keyring=dict(type="path"),
|
|
||||||
provenance=dict(type="bool", default=False),
|
|
||||||
repo_url=dict(type="str", aliases=["url", "chart_repo_url"]),
|
|
||||||
repo_username=dict(type="str", aliases=["username", "chart_repo_username"]),
|
|
||||||
repo_password=dict(
|
|
||||||
type="str", no_log=True, aliases=["password", "chart_repo_password"]
|
|
||||||
),
|
|
||||||
pass_credentials=dict(type="bool", default=False),
|
|
||||||
skip_tls_certs_check=dict(type="bool", default=False),
|
|
||||||
chart_devel=dict(type="bool"),
|
|
||||||
untar_chart=dict(type="bool", default=False),
|
|
||||||
destination=dict(type="path", required=True),
|
|
||||||
chart_ca_cert=dict(type="path"),
|
|
||||||
chart_ssl_cert_file=dict(type="path"),
|
|
||||||
chart_ssl_key_file=dict(type="path"),
|
|
||||||
binary_path=dict(type="path"),
|
|
||||||
)
|
|
||||||
module = AnsibleHelmModule(
|
|
||||||
argument_spec=argspec,
|
|
||||||
supports_check_mode=True,
|
|
||||||
required_by=dict(
|
|
||||||
repo_username=("repo_password"),
|
|
||||||
repo_password=("repo_username"),
|
|
||||||
),
|
|
||||||
mutually_exclusive=[("chart_version", "chart_devel")],
|
|
||||||
)
|
|
||||||
|
|
||||||
helm_version = module.get_helm_version()
|
|
||||||
if LooseVersion(helm_version) < LooseVersion("3.0.0"):
|
|
||||||
module.fail_json(
|
|
||||||
msg="This module requires helm >= 3.0.0, current version is {0}".format(
|
|
||||||
helm_version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
helm_pull_opt_versionning = dict(
|
|
||||||
skip_tls_certs_check="3.3.0",
|
|
||||||
chart_ca_cert="3.1.0",
|
|
||||||
chart_ssl_cert_file="3.1.0",
|
|
||||||
chart_ssl_key_file="3.1.0",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_version_requirement(opt):
|
|
||||||
req_version = helm_pull_opt_versionning.get(opt)
|
|
||||||
if req_version and LooseVersion(helm_version) < LooseVersion(req_version):
|
|
||||||
module.fail_json(
|
|
||||||
msg="Parameter {0} requires helm >= {1}, current version is {2}".format(
|
|
||||||
opt, req_version, helm_version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set `helm pull` arguments requiring values
|
|
||||||
helm_pull_opts = []
|
|
||||||
|
|
||||||
helm_value_args = dict(
|
|
||||||
chart_version="version",
|
|
||||||
verify_chart_keyring="keyring",
|
|
||||||
repo_url="repo",
|
|
||||||
repo_username="username",
|
|
||||||
repo_password="password",
|
|
||||||
destination="destination",
|
|
||||||
chart_ca_cert="ca-file",
|
|
||||||
chart_ssl_cert_file="cert-file",
|
|
||||||
chart_ssl_key_file="key-file",
|
|
||||||
)
|
|
||||||
|
|
||||||
for opt, cmdkey in helm_value_args.items():
|
|
||||||
if module.params.get(opt):
|
|
||||||
test_version_requirement(opt)
|
|
||||||
helm_pull_opts.append("--{0} {1}".format(cmdkey, module.params.get(opt)))
|
|
||||||
|
|
||||||
# Set `helm pull` arguments flags
|
|
||||||
helm_flag_args = dict(
|
|
||||||
verify_chart=dict(key="verify"),
|
|
||||||
provenance=dict(key="prov"),
|
|
||||||
pass_credentials=dict(key="pass-credentials"),
|
|
||||||
skip_tls_certs_check=dict(key="insecure-skip-tls-verify"),
|
|
||||||
chart_devel=dict(key="devel"),
|
|
||||||
untar_chart=dict(key="untar"),
|
|
||||||
)
|
|
||||||
|
|
||||||
for k, v in helm_flag_args.items():
|
|
||||||
if module.params.get(k):
|
|
||||||
test_version_requirement(k)
|
|
||||||
helm_pull_opts.append("--{0}".format(v["key"]))
|
|
||||||
|
|
||||||
helm_cmd_common = "{0} pull {1} {2}".format(
|
|
||||||
module.get_helm_binary(),
|
|
||||||
module.params.get("chart_ref"),
|
|
||||||
" ".join(helm_pull_opts),
|
|
||||||
)
|
|
||||||
if not module.check_mode:
|
|
||||||
rc, out, err = module.run_helm_command(helm_cmd_common, fails_on_error=False)
|
|
||||||
else:
|
|
||||||
rc, out, err = (0, "", "")
|
|
||||||
|
|
||||||
if rc == 0:
|
|
||||||
module.exit_json(
|
|
||||||
failed=False,
|
|
||||||
changed=True,
|
|
||||||
command=helm_cmd_common,
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
module.fail_json(
|
|
||||||
msg="Failure when executing Helm command.",
|
|
||||||
command=helm_cmd_common,
|
|
||||||
changed=False,
|
|
||||||
stdout=out,
|
|
||||||
stderr=err,
|
|
||||||
rc=rc,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -4,11 +4,10 @@
|
|||||||
# Copyright: (c) 2020, Ansible Project
|
# Copyright: (c) 2020, Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: helm_repository
|
module: helm_repository
|
||||||
|
|
||||||
@@ -65,63 +64,9 @@ options:
|
|||||||
default: present
|
default: present
|
||||||
aliases: [ state ]
|
aliases: [ state ]
|
||||||
type: str
|
type: str
|
||||||
pass_credentials:
|
'''
|
||||||
description:
|
|
||||||
- Pass credentials to all domains.
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: bool
|
|
||||||
version_added: 2.3.0
|
|
||||||
host:
|
|
||||||
description:
|
|
||||||
- Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
|
|
||||||
type: str
|
|
||||||
version_added: "2.3.0"
|
|
||||||
api_key:
|
|
||||||
description:
|
|
||||||
- Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable.
|
|
||||||
type: str
|
|
||||||
version_added: "2.3.0"
|
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL)
|
|
||||||
environment variable.
|
|
||||||
type: bool
|
|
||||||
aliases: [ verify_ssl ]
|
|
||||||
default: True
|
|
||||||
version_added: "2.3.0"
|
|
||||||
ca_cert:
|
|
||||||
description:
|
|
||||||
- Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
|
|
||||||
avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable.
|
|
||||||
type: path
|
|
||||||
aliases: [ ssl_ca_cert ]
|
|
||||||
version_added: "2.3.0"
|
|
||||||
context:
|
|
||||||
description:
|
|
||||||
- Helm option to specify which kubeconfig context to use.
|
|
||||||
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
|
|
||||||
type: str
|
|
||||||
aliases: [ kube_context ]
|
|
||||||
version_added: "2.4.0"
|
|
||||||
kubeconfig:
|
|
||||||
description:
|
|
||||||
- Helm option to specify kubeconfig path to use.
|
|
||||||
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
|
|
||||||
- The configuration can be provided as dictionary.
|
|
||||||
type: raw
|
|
||||||
aliases: [ kubeconfig_path ]
|
|
||||||
version_added: "2.4.0"
|
|
||||||
force_update:
|
|
||||||
description:
|
|
||||||
- Whether or not to replace (overwrite) the repo if it already exists.
|
|
||||||
type: bool
|
|
||||||
aliases: [ force ]
|
|
||||||
default: False
|
|
||||||
version_added: "2.4.0"
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Add a repository
|
- name: Add a repository
|
||||||
kubernetes.core.helm_repository:
|
kubernetes.core.helm_repository:
|
||||||
name: stable
|
name: stable
|
||||||
@@ -131,9 +76,9 @@ EXAMPLES = r"""
|
|||||||
kubernetes.core.helm_repository:
|
kubernetes.core.helm_repository:
|
||||||
name: redhat-charts
|
name: redhat-charts
|
||||||
repo_url: https://redhat-developer.github.com/redhat-helm-charts
|
repo_url: https://redhat-developer.github.com/redhat-helm-charts
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
stdout:
|
stdout:
|
||||||
type: str
|
type: str
|
||||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||||
@@ -164,81 +109,56 @@ msg:
|
|||||||
description: Error message returned by `helm` command
|
description: Error message returned by `helm` command
|
||||||
returned: on failure
|
returned: on failure
|
||||||
sample: 'Repository already have a repository named bitnami'
|
sample: 'Repository already have a repository named bitnami'
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import copy
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
IMP_YAML_ERR = None
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
IMP_YAML = False
|
IMP_YAML = False
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
||||||
AnsibleHelmModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
|
|
||||||
HELM_AUTH_ARG_SPEC,
|
|
||||||
HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Get repository from all repositories added
|
# Get repository from all repositories added
|
||||||
def get_repository(state, repo_name):
|
def get_repository(state, repo_name):
|
||||||
if state is not None:
|
if state is not None:
|
||||||
for repository in state:
|
for repository in state:
|
||||||
if repository["name"] == repo_name:
|
if repository['name'] == repo_name:
|
||||||
return repository
|
return repository
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Get repository status
|
# Get repository status
|
||||||
def get_repository_status(module, repository_name):
|
def get_repository_status(module, command, repository_name):
|
||||||
list_command = module.get_helm_binary() + " repo list --output=yaml"
|
list_command = command + " repo list --output=yaml"
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(list_command, fails_on_error=False)
|
rc, out, err = run_helm(module, list_command, fails_on_error=False)
|
||||||
|
|
||||||
# no repo => rc=1 and 'no repositories to show' in output
|
# no repo => rc=1 and 'no repositories to show' in output
|
||||||
if rc == 1 and "no repositories to show" in err:
|
if rc == 1 and "no repositories to show" in err:
|
||||||
return None
|
return None
|
||||||
elif rc != 0:
|
elif rc != 0:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||||
rc, out, err
|
command=list_command
|
||||||
),
|
|
||||||
command=list_command,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return get_repository(yaml.safe_load(out), repository_name)
|
return get_repository(yaml.safe_load(out), repository_name)
|
||||||
|
|
||||||
|
|
||||||
# Install repository
|
# Install repository
|
||||||
def install_repository(
|
def install_repository(command, repository_name, repository_url, repository_username, repository_password):
|
||||||
command,
|
|
||||||
repository_name,
|
|
||||||
repository_url,
|
|
||||||
repository_username,
|
|
||||||
repository_password,
|
|
||||||
pass_credentials,
|
|
||||||
force_update,
|
|
||||||
):
|
|
||||||
install_command = command + " repo add " + repository_name + " " + repository_url
|
install_command = command + " repo add " + repository_name + " " + repository_url
|
||||||
|
|
||||||
if repository_username is not None and repository_password is not None:
|
if repository_username is not None and repository_password is not None:
|
||||||
install_command += " --username=" + repository_username
|
install_command += " --username=" + repository_username
|
||||||
install_command += " --password=" + repository_password
|
install_command += " --password=" + repository_password
|
||||||
|
|
||||||
if pass_credentials:
|
|
||||||
install_command += " --pass-credentials"
|
|
||||||
|
|
||||||
if force_update:
|
|
||||||
install_command += " --force-update"
|
|
||||||
|
|
||||||
return install_command
|
return install_command
|
||||||
|
|
||||||
|
|
||||||
@@ -249,32 +169,24 @@ def delete_repository(command, repository_name):
|
|||||||
return remove_command
|
return remove_command
|
||||||
|
|
||||||
|
|
||||||
def argument_spec():
|
|
||||||
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
|
|
||||||
arg_spec.update(
|
|
||||||
dict(
|
|
||||||
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"]
|
|
||||||
),
|
|
||||||
pass_credentials=dict(type="bool", default=False, no_log=True),
|
|
||||||
force_update=dict(type="bool", default=False, aliases=["force"]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return arg_spec
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global module
|
global module
|
||||||
|
|
||||||
module = AnsibleHelmModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec(),
|
argument_spec=dict(
|
||||||
required_together=[["repo_username", "repo_password"]],
|
binary_path=dict(type='path'),
|
||||||
required_if=[("repo_state", "present", ["repo_url"])],
|
repo_name=dict(type='str', aliases=['name'], required=True),
|
||||||
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
|
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']),
|
||||||
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -283,58 +195,48 @@ def main():
|
|||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
repo_name = module.params.get("repo_name")
|
bin_path = module.params.get('binary_path')
|
||||||
repo_url = module.params.get("repo_url")
|
repo_name = module.params.get('repo_name')
|
||||||
repo_username = module.params.get("repo_username")
|
repo_url = module.params.get('repo_url')
|
||||||
repo_password = module.params.get("repo_password")
|
repo_username = module.params.get('repo_username')
|
||||||
repo_state = module.params.get("repo_state")
|
repo_password = module.params.get('repo_password')
|
||||||
pass_credentials = module.params.get("pass_credentials")
|
repo_state = module.params.get('repo_state')
|
||||||
force_update = module.params.get("force_update")
|
|
||||||
|
|
||||||
helm_cmd = module.get_helm_binary()
|
if bin_path is not None:
|
||||||
|
helm_cmd = bin_path
|
||||||
|
else:
|
||||||
|
helm_cmd = module.get_bin_path('helm', required=True)
|
||||||
|
|
||||||
repository_status = get_repository_status(module, repo_name)
|
repository_status = get_repository_status(module, helm_cmd, repo_name)
|
||||||
|
|
||||||
if repo_state == "absent" and repository_status is not None:
|
if repo_state == "absent" and repository_status is not None:
|
||||||
helm_cmd = delete_repository(helm_cmd, repo_name)
|
helm_cmd = delete_repository(helm_cmd, repo_name)
|
||||||
changed = True
|
changed = True
|
||||||
elif repo_state == "present":
|
elif repo_state == "present":
|
||||||
if repository_status is None or force_update:
|
if repository_status is None:
|
||||||
helm_cmd = install_repository(
|
helm_cmd = install_repository(helm_cmd, repo_name, repo_url, repo_username, repo_password)
|
||||||
helm_cmd,
|
|
||||||
repo_name,
|
|
||||||
repo_url,
|
|
||||||
repo_username,
|
|
||||||
repo_password,
|
|
||||||
pass_credentials,
|
|
||||||
force_update,
|
|
||||||
)
|
|
||||||
changed = True
|
changed = True
|
||||||
elif repository_status["url"] != repo_url:
|
elif repository_status['url'] != repo_url:
|
||||||
module.fail_json(
|
module.fail_json(msg="Repository already have a repository named {0}".format(repo_name))
|
||||||
msg="Repository already have a repository named {0}".format(repo_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
module.exit_json(changed=changed)
|
module.exit_json(changed=changed)
|
||||||
elif not changed:
|
elif not changed:
|
||||||
module.exit_json(changed=False, repo_name=repo_name, repo_url=repo_url)
|
module.exit_json(changed=False, repo_name=repo_name, repo_url=repo_url)
|
||||||
|
|
||||||
rc, out, err = module.run_helm_command(helm_cmd)
|
rc, out, err = run_helm(module, helm_cmd)
|
||||||
|
|
||||||
if repo_password is not None:
|
if repo_password is not None:
|
||||||
helm_cmd = helm_cmd.replace(repo_password, "******")
|
helm_cmd = helm_cmd.replace(repo_password, '******')
|
||||||
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||||
rc, out, err
|
command=helm_cmd
|
||||||
),
|
|
||||||
command=helm_cmd,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
|
module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: helm_template
|
module: helm_template
|
||||||
|
|
||||||
@@ -45,21 +45,6 @@ options:
|
|||||||
- Chart version to use. If this is not specified, the latest version is installed.
|
- Chart version to use. If this is not specified, the latest version is installed.
|
||||||
required: false
|
required: false
|
||||||
type: str
|
type: str
|
||||||
dependency_update:
|
|
||||||
description:
|
|
||||||
- Run helm dependency update before the operation.
|
|
||||||
- The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
|
|
||||||
- For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
|
|
||||||
default: false
|
|
||||||
type: bool
|
|
||||||
aliases: [ dep_up ]
|
|
||||||
version_added: "2.4.0"
|
|
||||||
disable_hook:
|
|
||||||
description:
|
|
||||||
- Prevent hooks from running during install.
|
|
||||||
default: False
|
|
||||||
type: bool
|
|
||||||
version_added: 2.4.0
|
|
||||||
include_crds:
|
include_crds:
|
||||||
description:
|
description:
|
||||||
- Include custom resource descriptions in rendered templates.
|
- Include custom resource descriptions in rendered templates.
|
||||||
@@ -72,19 +57,6 @@ options:
|
|||||||
- If the directory already exists, it will be overwritten.
|
- If the directory already exists, it will be overwritten.
|
||||||
required: false
|
required: false
|
||||||
type: path
|
type: path
|
||||||
release_name:
|
|
||||||
description:
|
|
||||||
- Release name to use in rendered templates.
|
|
||||||
required: false
|
|
||||||
aliases: [ name ]
|
|
||||||
type: str
|
|
||||||
version_added: 2.4.0
|
|
||||||
release_namespace:
|
|
||||||
description:
|
|
||||||
- namespace scope for this request.
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
version_added: 2.4.0
|
|
||||||
release_values:
|
release_values:
|
||||||
description:
|
description:
|
||||||
- Values to pass to chart.
|
- Values to pass to chart.
|
||||||
@@ -92,13 +64,6 @@ options:
|
|||||||
default: {}
|
default: {}
|
||||||
aliases: [ values ]
|
aliases: [ values ]
|
||||||
type: dict
|
type: dict
|
||||||
show_only:
|
|
||||||
description:
|
|
||||||
- Only show manifests rendered from the given templates.
|
|
||||||
required: false
|
|
||||||
type: list
|
|
||||||
elements: str
|
|
||||||
version_added: 2.4.0
|
|
||||||
values_files:
|
values_files:
|
||||||
description:
|
description:
|
||||||
- Value files to pass to chart.
|
- Value files to pass to chart.
|
||||||
@@ -114,34 +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.
|
- Run C(helm repo update) before the operation. Can be run as part of the template generation or as a separate step.
|
||||||
default: false
|
default: false
|
||||||
type: bool
|
type: bool
|
||||||
set_values:
|
'''
|
||||||
description:
|
|
||||||
- Values to pass to chart configuration.
|
|
||||||
required: false
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
value:
|
|
||||||
description:
|
|
||||||
- Value to pass to chart configuration (e.g phase=prod).
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
value_type:
|
|
||||||
description:
|
|
||||||
- Use C(raw) set individual value.
|
|
||||||
- Use C(string) to force a string for an individual value.
|
|
||||||
- Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
|
|
||||||
- Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
|
|
||||||
default: raw
|
|
||||||
choices:
|
|
||||||
- raw
|
|
||||||
- string
|
|
||||||
- json
|
|
||||||
- file
|
|
||||||
version_added: '2.4.0'
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Render templates to specified directory
|
- name: Render templates to specified directory
|
||||||
kubernetes.core.helm_template:
|
kubernetes.core.helm_template:
|
||||||
chart_ref: stable/prometheus
|
chart_ref: stable/prometheus
|
||||||
@@ -156,27 +96,9 @@ EXAMPLES = r"""
|
|||||||
copy:
|
copy:
|
||||||
dest: myfile.yaml
|
dest: myfile.yaml
|
||||||
content: "{{ result.stdout }}"
|
content: "{{ result.stdout }}"
|
||||||
|
'''
|
||||||
|
|
||||||
- name: Render MutatingWebhooksConfiguration for revision tag "canary", rev "1-13-0"
|
RETURN = r'''
|
||||||
kubernetes.core.helm_template:
|
|
||||||
chart_ref: istio/istiod
|
|
||||||
chart_version: "1.13.0"
|
|
||||||
release_namespace: "istio-system"
|
|
||||||
show_only:
|
|
||||||
- "templates/revision-tags.yaml"
|
|
||||||
release_values:
|
|
||||||
revision: "1-13-0"
|
|
||||||
revisionTags:
|
|
||||||
- "canary"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Write templates to file
|
|
||||||
copy:
|
|
||||||
dest: myfile.yaml
|
|
||||||
content: "{{ result.stdout }}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
stdout:
|
stdout:
|
||||||
type: str
|
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.
|
description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents.
|
||||||
@@ -192,51 +114,25 @@ 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.
|
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
|
returned: always
|
||||||
sample: helm template --output-dir mychart nginx-stable/nginx-ingress
|
sample: helm template --output-dir mychart nginx-stable/nginx-ingress
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
IMP_YAML_ERR = None
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
IMP_YAML = False
|
IMP_YAML = False
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
||||||
AnsibleHelmModule,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def template(
|
def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir=None,
|
||||||
cmd,
|
release_values=None, values_files=None, include_crds=False):
|
||||||
chart_ref,
|
cmd += " template " + chart_ref
|
||||||
chart_repo_url=None,
|
|
||||||
chart_version=None,
|
|
||||||
dependency_update=None,
|
|
||||||
disable_hook=None,
|
|
||||||
output_dir=None,
|
|
||||||
show_only=None,
|
|
||||||
release_name=None,
|
|
||||||
release_namespace=None,
|
|
||||||
release_values=None,
|
|
||||||
values_files=None,
|
|
||||||
include_crds=False,
|
|
||||||
set_values=None,
|
|
||||||
):
|
|
||||||
cmd += " template "
|
|
||||||
|
|
||||||
if release_name:
|
|
||||||
cmd += release_name + " "
|
|
||||||
|
|
||||||
cmd += chart_ref
|
|
||||||
|
|
||||||
if dependency_update:
|
|
||||||
cmd += " --dependency-update"
|
|
||||||
|
|
||||||
if chart_repo_url:
|
if chart_repo_url:
|
||||||
cmd += " --repo=" + chart_repo_url
|
cmd += " --repo=" + chart_repo_url
|
||||||
@@ -244,116 +140,81 @@ def template(
|
|||||||
if chart_version:
|
if chart_version:
|
||||||
cmd += " --version=" + chart_version
|
cmd += " --version=" + chart_version
|
||||||
|
|
||||||
if disable_hook:
|
|
||||||
cmd += " --no-hooks"
|
|
||||||
|
|
||||||
if output_dir:
|
if output_dir:
|
||||||
cmd += " --output-dir=" + output_dir
|
cmd += " --output-dir=" + output_dir
|
||||||
|
|
||||||
if show_only:
|
if release_values:
|
||||||
for template in show_only:
|
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||||
cmd += " -s " + template
|
with open(path, 'w') as yaml_file:
|
||||||
|
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||||
|
cmd += " -f=" + path
|
||||||
|
|
||||||
if values_files:
|
if values_files:
|
||||||
for values_file in values_files:
|
for values_file in values_files:
|
||||||
cmd += " -f=" + values_file
|
cmd += " -f=" + values_file
|
||||||
|
|
||||||
if release_namespace:
|
|
||||||
cmd += " -n " + release_namespace
|
|
||||||
|
|
||||||
if release_values:
|
|
||||||
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
|
|
||||||
|
|
||||||
if include_crds:
|
if include_crds:
|
||||||
cmd += " --include-crds"
|
cmd += " --include-crds"
|
||||||
|
|
||||||
if set_values:
|
|
||||||
cmd += " " + set_values
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleHelmModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type="path"),
|
binary_path=dict(type='path'),
|
||||||
chart_ref=dict(type="path", required=True),
|
chart_ref=dict(type='path', required=True),
|
||||||
chart_repo_url=dict(type="str"),
|
chart_repo_url=dict(type='str'),
|
||||||
chart_version=dict(type="str"),
|
chart_version=dict(type='str'),
|
||||||
dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
|
include_crds=dict(type='bool', default=False),
|
||||||
disable_hook=dict(type="bool", default=False),
|
output_dir=dict(type='path'),
|
||||||
include_crds=dict(type="bool", default=False),
|
release_values=dict(type='dict', default={}, aliases=['values']),
|
||||||
release_name=dict(type="str", aliases=["name"]),
|
values_files=dict(type='list', default=[], elements='str'),
|
||||||
output_dir=dict(type="path"),
|
update_repo_cache=dict(type='bool', default=False)
|
||||||
release_namespace=dict(type="str"),
|
|
||||||
release_values=dict(type="dict", default={}, aliases=["values"]),
|
|
||||||
show_only=dict(type="list", default=[], elements="str"),
|
|
||||||
values_files=dict(type="list", default=[], elements="str"),
|
|
||||||
update_repo_cache=dict(type="bool", default=False),
|
|
||||||
set_values=dict(type="list", elements="dict"),
|
|
||||||
),
|
),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
check_mode = module.check_mode
|
check_mode = module.check_mode
|
||||||
chart_ref = module.params.get("chart_ref")
|
bin_path = module.params.get('binary_path')
|
||||||
chart_repo_url = module.params.get("chart_repo_url")
|
chart_ref = module.params.get('chart_ref')
|
||||||
chart_version = module.params.get("chart_version")
|
chart_repo_url = module.params.get('chart_repo_url')
|
||||||
dependency_update = module.params.get("dependency_update")
|
chart_version = module.params.get('chart_version')
|
||||||
disable_hook = module.params.get("disable_hook")
|
include_crds = module.params.get('include_crds')
|
||||||
include_crds = module.params.get("include_crds")
|
output_dir = module.params.get('output_dir')
|
||||||
release_name = module.params.get("release_name")
|
release_values = module.params.get('release_values')
|
||||||
output_dir = module.params.get("output_dir")
|
values_files = module.params.get('values_files')
|
||||||
show_only = module.params.get("show_only")
|
update_repo_cache = module.params.get('update_repo_cache')
|
||||||
release_namespace = module.params.get("release_namespace")
|
|
||||||
release_values = module.params.get("release_values")
|
|
||||||
values_files = module.params.get("values_files")
|
|
||||||
update_repo_cache = module.params.get("update_repo_cache")
|
|
||||||
set_values = module.params.get("set_values")
|
|
||||||
|
|
||||||
if not IMP_YAML:
|
if not IMP_YAML:
|
||||||
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
|
||||||
|
|
||||||
helm_cmd = module.get_helm_binary()
|
helm_cmd = bin_path or module.get_bin_path('helm', required=True)
|
||||||
|
|
||||||
if update_repo_cache:
|
if update_repo_cache:
|
||||||
update_cmd = helm_cmd + " repo update"
|
update_cmd = helm_cmd + " repo update"
|
||||||
module.run_helm_command(update_cmd)
|
run_helm(module, update_cmd)
|
||||||
|
|
||||||
set_values_args = None
|
tmpl_cmd = template(helm_cmd, chart_ref, chart_repo_url=chart_repo_url,
|
||||||
if set_values:
|
chart_version=chart_version, output_dir=output_dir,
|
||||||
set_values_args = module.get_helm_set_values_args(set_values)
|
release_values=release_values, values_files=values_files,
|
||||||
|
include_crds=include_crds)
|
||||||
tmpl_cmd = template(
|
|
||||||
helm_cmd,
|
|
||||||
chart_ref,
|
|
||||||
dependency_update=dependency_update,
|
|
||||||
chart_repo_url=chart_repo_url,
|
|
||||||
chart_version=chart_version,
|
|
||||||
disable_hook=disable_hook,
|
|
||||||
release_name=release_name,
|
|
||||||
output_dir=output_dir,
|
|
||||||
release_namespace=release_namespace,
|
|
||||||
release_values=release_values,
|
|
||||||
show_only=show_only,
|
|
||||||
values_files=values_files,
|
|
||||||
include_crds=include_crds,
|
|
||||||
set_values=set_values_args,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not check_mode:
|
if not check_mode:
|
||||||
rc, out, err = module.run_helm_command(tmpl_cmd)
|
rc, out, err = run_helm(module, tmpl_cmd)
|
||||||
else:
|
else:
|
||||||
out = err = ""
|
out = err = ""
|
||||||
rc = 0
|
rc = 0
|
||||||
|
|
||||||
module.exit_json(
|
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()
|
main()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s
|
module: k8s
|
||||||
|
|
||||||
@@ -142,45 +142,15 @@ options:
|
|||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
version_added: 2.2.0
|
version_added: 2.2.0
|
||||||
generate_name:
|
|
||||||
description:
|
|
||||||
- Use to specify the basis of an object name and random characters will be added automatically on server to generate a unique name.
|
|
||||||
- This option is ignored when I(state) is not set to C(present) or when I(apply) is set to C(yes).
|
|
||||||
- If I(resource definition) is provided, the I(metadata.generateName) value from the I(resource_definition)
|
|
||||||
will override this option.
|
|
||||||
- If I(resource definition) is provided, and contains I(metadata.name), this option is ignored.
|
|
||||||
- mutually exclusive with C(name).
|
|
||||||
type: str
|
|
||||||
version_added: 2.3.0
|
|
||||||
server_side_apply:
|
|
||||||
description:
|
|
||||||
- When this option is set, apply runs in the server instead of the client.
|
|
||||||
- Ignored if C(apply) is not set or is set to False.
|
|
||||||
- This option requires "kubernetes >= 19.15.0".
|
|
||||||
type: dict
|
|
||||||
version_added: 2.3.0
|
|
||||||
suboptions:
|
|
||||||
field_manager:
|
|
||||||
type: str
|
|
||||||
description:
|
|
||||||
- Name of the manager used to track field ownership.
|
|
||||||
required: True
|
|
||||||
force_conflicts:
|
|
||||||
description:
|
|
||||||
- A conflict is a special status error that occurs when an Server Side Apply operation tries to change a field,
|
|
||||||
which another user also claims to manage.
|
|
||||||
- When set to True, server-side apply will force the changes against conflicts.
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
- "jsonpatch"
|
- "jsonpatch"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Create a k8s namespace
|
- name: Create a k8s namespace
|
||||||
kubernetes.core.k8s:
|
kubernetes.core.k8s:
|
||||||
name: testing
|
name: testing
|
||||||
@@ -232,14 +202,6 @@ EXAMPLES = r"""
|
|||||||
state: present
|
state: present
|
||||||
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
|
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
|
||||||
|
|
||||||
- name: >-
|
|
||||||
(Alternative) Read definition file from the Ansible controller file system.
|
|
||||||
In this case, the definition file contains multiple YAML documents, separated by ---.
|
|
||||||
If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
|
|
||||||
kubernetes.core.k8s:
|
|
||||||
state: present
|
|
||||||
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml_all }}"
|
|
||||||
|
|
||||||
- name: Read definition template file from the Ansible controller file system
|
- name: Read definition template file from the Ansible controller file system
|
||||||
kubernetes.core.k8s:
|
kubernetes.core.k8s:
|
||||||
state: present
|
state: present
|
||||||
@@ -316,36 +278,9 @@ EXAMPLES = r"""
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
support: patch
|
support: patch
|
||||||
|
'''
|
||||||
|
|
||||||
# Create object using generateName
|
RETURN = r'''
|
||||||
- name: create resource using name generated by the server
|
|
||||||
kubernetes.core.k8s:
|
|
||||||
state: present
|
|
||||||
generate_name: pod-
|
|
||||||
definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: py
|
|
||||||
image: python:3.7-alpine
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
|
|
||||||
# Server side apply
|
|
||||||
- name: Create configmap using server side apply
|
|
||||||
kubernetes.core.k8s:
|
|
||||||
namespace: testing
|
|
||||||
definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: my-configmap
|
|
||||||
apply: yes
|
|
||||||
server_side_apply:
|
|
||||||
field_manager: ansible
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
|
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
|
||||||
@@ -385,43 +320,20 @@ result:
|
|||||||
description: error while trying to create/delete the object.
|
description: error while trying to create/delete the object.
|
||||||
returned: error
|
returned: error
|
||||||
type: complex
|
type: complex
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
|
||||||
WAIT_ARG_SPEC,
|
|
||||||
NAME_ARG_SPEC,
|
|
||||||
RESOURCE_ARG_SPEC,
|
|
||||||
DELETE_OPTS_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
|
|
||||||
run_module,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_spec():
|
def validate_spec():
|
||||||
return dict(
|
return dict(
|
||||||
fail_on_error=dict(type="bool"),
|
fail_on_error=dict(type='bool'),
|
||||||
version=dict(),
|
version=dict(),
|
||||||
strict=dict(type="bool", default=True),
|
strict=dict(type='bool', default=True)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def server_apply_spec():
|
|
||||||
return dict(
|
|
||||||
field_manager=dict(type="str", required=True),
|
|
||||||
force_conflicts=dict(type="bool", default=False),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -430,50 +342,56 @@ def argspec():
|
|||||||
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
|
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
|
||||||
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
|
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
|
||||||
argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
|
argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
|
||||||
argument_spec["merge_type"] = dict(
|
argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
|
||||||
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["validate"] = dict(type="dict", default=None, options=validate_spec())
|
argument_spec['apply'] = dict(type='bool', default=False)
|
||||||
argument_spec["append_hash"] = dict(type="bool", default=False)
|
argument_spec['template'] = dict(type='raw', default=None)
|
||||||
argument_spec["apply"] = dict(type="bool", default=False)
|
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
|
||||||
argument_spec["template"] = dict(type="raw", default=None)
|
argument_spec['continue_on_error'] = dict(type='bool', default=False)
|
||||||
argument_spec["delete_options"] = dict(
|
argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched'])
|
||||||
type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)
|
argument_spec['force'] = dict(type='bool', default=False)
|
||||||
)
|
argument_spec['label_selectors'] = dict(type='list', elements='str')
|
||||||
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["server_side_apply"] = dict(
|
|
||||||
type="dict", default=None, options=server_apply_spec()
|
|
||||||
)
|
|
||||||
|
|
||||||
return argument_spec
|
return argument_spec
|
||||||
|
|
||||||
|
|
||||||
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
|
k8s_ansible_mixin.module = module
|
||||||
|
k8s_ansible_mixin.argspec = module.argument_spec
|
||||||
|
k8s_ansible_mixin.check_mode = k8s_ansible_mixin.module.check_mode
|
||||||
|
k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
|
||||||
|
k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
|
||||||
|
k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
|
||||||
|
k8s_ansible_mixin.exit_json = k8s_ansible_mixin.module.exit_json
|
||||||
|
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.namespace = k8s_ansible_mixin.params.get('namespace')
|
||||||
|
|
||||||
|
k8s_ansible_mixin.check_library_version()
|
||||||
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
|
k8s_ansible_mixin.execute_module()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
("resource_definition", "src"),
|
('resource_definition', 'src'),
|
||||||
("merge_type", "apply"),
|
('merge_type', 'apply'),
|
||||||
("template", "resource_definition"),
|
('template', 'resource_definition'),
|
||||||
("template", "src"),
|
('template', 'src'),
|
||||||
("name", "generate_name"),
|
|
||||||
]
|
]
|
||||||
|
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)
|
||||||
|
|
||||||
module = AnsibleK8SModule(
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
module_class=AnsibleModule,
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
argument_spec=argspec(),
|
execute_module(module, k8s_ansible_mixin)
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
run_module(module)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
module: k8s_cluster_info
|
module: k8s_cluster_info
|
||||||
|
|
||||||
version_added: "0.11.1"
|
version_added: "0.11.1"
|
||||||
@@ -37,9 +36,9 @@ requirements:
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Get Cluster information
|
- name: Get Cluster information
|
||||||
kubernetes.core.k8s_cluster_info:
|
kubernetes.core.k8s_cluster_info:
|
||||||
register: api_status
|
register: api_status
|
||||||
@@ -48,9 +47,9 @@ EXAMPLES = r"""
|
|||||||
kubernetes.core.k8s_cluster_info:
|
kubernetes.core.k8s_cluster_info:
|
||||||
invalidate_cache: False
|
invalidate_cache: False
|
||||||
register: api_status
|
register: api_status
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
connection:
|
connection:
|
||||||
description:
|
description:
|
||||||
- Connection information
|
- Connection information
|
||||||
@@ -137,96 +136,78 @@ apis:
|
|||||||
description: Resource singular name
|
description: Resource singular name
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
"""
|
'''
|
||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import traceback
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
HAS_K8S = False
|
||||||
try:
|
try:
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
||||||
ResourceList,
|
HAS_K8S = True
|
||||||
)
|
except ImportError as e:
|
||||||
except ImportError:
|
K8S_IMP_ERR = e
|
||||||
# Handled during module setup
|
K8S_IMP_EXC = traceback.format_exc()
|
||||||
pass
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible.module_utils._text import to_native
|
||||||
AnsibleModule,
|
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.k8s.core import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleK8SModule,
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
|
||||||
AUTH_ARG_SPEC,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, client):
|
def execute_module(module, client):
|
||||||
if module.params.get("invalidate_cache"):
|
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
|
||||||
|
if invalidate_cache:
|
||||||
client.resources.invalidate_cache()
|
client.resources.invalidate_cache()
|
||||||
results = defaultdict(dict)
|
results = defaultdict(dict)
|
||||||
for resource in list(client.resources):
|
for resource in list(client.resources):
|
||||||
resource = resource[0]
|
resource = resource[0]
|
||||||
if isinstance(resource, ResourceList):
|
if isinstance(resource, ResourceList):
|
||||||
continue
|
continue
|
||||||
key = (
|
key = resource.group_version if resource.group == '' else '/'.join([resource.group, resource.group_version.split('/')[-1]])
|
||||||
resource.group_version
|
|
||||||
if resource.group == ""
|
|
||||||
else "/".join([resource.group, resource.group_version.split("/")[-1]])
|
|
||||||
)
|
|
||||||
results[key][resource.kind] = {
|
results[key][resource.kind] = {
|
||||||
"categories": resource.categories if resource.categories else [],
|
'categories': resource.categories if resource.categories else [],
|
||||||
"name": resource.name,
|
'name': resource.name,
|
||||||
"namespaced": resource.namespaced,
|
'namespaced': resource.namespaced,
|
||||||
"preferred": resource.preferred,
|
'preferred': resource.preferred,
|
||||||
"short_names": resource.short_names if resource.short_names else [],
|
'short_names': resource.short_names if resource.short_names else [],
|
||||||
"singular_name": resource.singular_name,
|
'singular_name': resource.singular_name,
|
||||||
}
|
}
|
||||||
configuration = client.configuration
|
configuration = client.configuration
|
||||||
connection = {
|
connection = {
|
||||||
"cert_file": configuration.cert_file,
|
'cert_file': configuration.cert_file,
|
||||||
"host": configuration.host,
|
'host': configuration.host,
|
||||||
"password": configuration.password,
|
'password': configuration.password,
|
||||||
"proxy": configuration.proxy,
|
'proxy': configuration.proxy,
|
||||||
"ssl_ca_cert": configuration.ssl_ca_cert,
|
'ssl_ca_cert': configuration.ssl_ca_cert,
|
||||||
"username": configuration.username,
|
'username': configuration.username,
|
||||||
"verify_ssl": configuration.verify_ssl,
|
'verify_ssl': configuration.verify_ssl,
|
||||||
}
|
}
|
||||||
from kubernetes import __version__ as version
|
from kubernetes import __version__ as version
|
||||||
|
|
||||||
version_info = {
|
version_info = {
|
||||||
"client": version,
|
'client': version,
|
||||||
"server": client.client.version,
|
'server': client.version,
|
||||||
}
|
}
|
||||||
module.exit_json(
|
module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
|
||||||
changed=False, apis=results, connection=connection, version=version_info
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
def argspec():
|
||||||
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
spec["invalidate_cache"] = dict(type="bool", default=True)
|
spec['invalidate_cache'] = dict(type='bool', default=True)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
module_class=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.k8s.client import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import get_api_client
|
||||||
get_api_client,
|
execute_module(module, client=get_api_client(module=module))
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
execute_module(module, client=get_api_client(module=module))
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s_cp
|
module: k8s_cp
|
||||||
|
|
||||||
@@ -79,9 +78,9 @@ options:
|
|||||||
|
|
||||||
notes:
|
notes:
|
||||||
- the tar binary is required on the container when copying from local filesystem to pod.
|
- 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
|
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
|
||||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
|
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
|
||||||
kubernetes.core.k8s_cp:
|
kubernetes.core.k8s_cp:
|
||||||
@@ -119,107 +118,376 @@ EXAMPLES = r"""
|
|||||||
state: from_pod
|
state: from_pod
|
||||||
|
|
||||||
# copy content into a file in the remote pod
|
# copy content into a file in the remote pod
|
||||||
- name: Copy content into a file in the remote pod
|
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||||
kubernetes.core.k8s_cp:
|
kubernetes.core.k8s_cp:
|
||||||
state: to_pod
|
state: to_pod
|
||||||
namespace: some-namespace
|
namespace: some-namespace
|
||||||
pod: some-pod
|
pod: some-pod
|
||||||
remote_path: /tmp/foo.txt
|
remote_path: /tmp/foo.txt
|
||||||
content: "This content will be copied into remote file"
|
content: "This content will be copied into remote file"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- message describing the copy operation successfully done.
|
- message describing the copy operation successfully done.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import os
|
||||||
|
from tempfile import TemporaryFile, NamedTemporaryFile
|
||||||
|
from select import select
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
import tarfile
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleModule,
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
|
||||||
AUTH_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.copy import (
|
|
||||||
K8SCopyFromPod,
|
|
||||||
K8SCopyToPod,
|
|
||||||
check_pod,
|
|
||||||
)
|
|
||||||
from ansible.module_utils._text import to_native
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
# ImportError are managed by the common module already.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
class K8SCopy(metaclass=ABCMeta):
|
||||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
|
||||||
argument_spec["namespace"] = {"type": "str", "required": True}
|
def __init__(self, module, client):
|
||||||
argument_spec["pod"] = {"type": "str", "required": True}
|
self.client = client
|
||||||
argument_spec["container"] = {}
|
self.module = module
|
||||||
argument_spec["remote_path"] = {"type": "path", "required": True}
|
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
||||||
argument_spec["local_path"] = {"type": "path"}
|
|
||||||
argument_spec["content"] = {"type": "str"}
|
self.local_path = module.params.get('local_path')
|
||||||
argument_spec["state"] = {
|
self.name = module.params.get('pod')
|
||||||
"type": "str",
|
self.namespace = module.params.get('namespace')
|
||||||
"default": "to_pod",
|
self.remote_path = module.params.get('remote_path')
|
||||||
"choices": ["to_pod", "from_pod"],
|
self.content = module.params.get('content')
|
||||||
}
|
|
||||||
argument_spec["no_preserve"] = {"type": "bool", "default": False}
|
self.no_preserve = module.params.get('no_preserve')
|
||||||
return argument_spec
|
self.container_arg = {}
|
||||||
|
if module.params.get('container'):
|
||||||
|
self.container_arg['container'] = module.params.get('container')
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def run(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
self.files_to_copy = list()
|
||||||
|
|
||||||
|
def list_remote_files(self):
|
||||||
|
"""
|
||||||
|
This method will check if the remote path is a dir or file
|
||||||
|
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)
|
||||||
|
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)))
|
||||||
|
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'))
|
||||||
|
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))
|
||||||
|
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))
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
self.stdout = None
|
||||||
|
self.stderr = None
|
||||||
|
|
||||||
|
if self.response.is_open():
|
||||||
|
if not self.response.sock.connected:
|
||||||
|
self.response._connected = False
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
channel = frame.data[0]
|
||||||
|
content = frame.data[1:]
|
||||||
|
if content:
|
||||||
|
if channel == STDOUT_CHANNEL:
|
||||||
|
self.stdout = content
|
||||||
|
elif channel == STDERR_CHANNEL:
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
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))
|
||||||
|
# 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)
|
||||||
|
errors = []
|
||||||
|
with open(dest_file, 'wb') as fh:
|
||||||
|
while self.response._connected:
|
||||||
|
self.read()
|
||||||
|
if self.stdout:
|
||||||
|
fh.write(self.stdout)
|
||||||
|
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))
|
||||||
|
|
||||||
|
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.copy()
|
||||||
|
except Exception as 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)
|
||||||
|
errors = []
|
||||||
|
while response.is_open():
|
||||||
|
response.update(timeout=1)
|
||||||
|
if response.peek_stderr():
|
||||||
|
errors.append(response.read_stderr())
|
||||||
|
response.close()
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close_temp_file(self):
|
||||||
|
if self.named_temp_file:
|
||||||
|
self.named_temp_file.close()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
# remove trailing slash from destination path
|
||||||
|
dest_file = self.remote_path.rstrip("/")
|
||||||
|
src_file = self.local_path
|
||||||
|
self.named_temp_file = None
|
||||||
|
if self.content:
|
||||||
|
self.named_temp_file = NamedTemporaryFile(mode="w")
|
||||||
|
self.named_temp_file.write(self.content)
|
||||||
|
self.named_temp_file.flush()
|
||||||
|
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))
|
||||||
|
if not os.access(self.local_path, os.R_OK):
|
||||||
|
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")
|
||||||
|
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', '-']
|
||||||
|
else:
|
||||||
|
tar_command = ['tar', '-xmf', '-']
|
||||||
|
|
||||||
|
if dest_file.startswith("/"):
|
||||||
|
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)
|
||||||
|
with TemporaryFile() as tar_buffer:
|
||||||
|
with tarfile.open(fileobj=tar_buffer, mode='w') as tar:
|
||||||
|
tar.add(src_file, dest_file)
|
||||||
|
tar_buffer.seek(0)
|
||||||
|
commands = []
|
||||||
|
# push command in chunk mode
|
||||||
|
size = 1024 * 1024
|
||||||
|
while True:
|
||||||
|
data = tar_buffer.read(size)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
commands.append(data)
|
||||||
|
|
||||||
|
stderr, stdout = [], []
|
||||||
|
while response.is_open():
|
||||||
|
if response.peek_stdout():
|
||||||
|
stdout.append(response.read_stdout().rstrip("\n"))
|
||||||
|
if response.peek_stderr():
|
||||||
|
stderr.append(response.read_stderr().rstrip("\n"))
|
||||||
|
if commands:
|
||||||
|
cmd = commands.pop(0)
|
||||||
|
response.write_stdin(cmd)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
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.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))
|
||||||
|
|
||||||
|
except Exception as 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')
|
||||||
|
|
||||||
|
def _fail(exc):
|
||||||
|
arg = {}
|
||||||
|
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']:
|
||||||
|
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']]
|
||||||
|
if container and container not in containers:
|
||||||
|
module.fail_json(msg="Pod has no container {0}".format(container))
|
||||||
|
return containers
|
||||||
|
except Exception as exc:
|
||||||
|
_fail(exc)
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module):
|
def execute_module(module):
|
||||||
client = get_api_client(module=module)
|
|
||||||
svc = K8sService(client, module)
|
|
||||||
containers = check_pod(svc)
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
state = module.params.get("state")
|
k8s_ansible_mixin = K8sAnsibleMixin(module, pyyaml_required=False)
|
||||||
if state == "to_pod":
|
k8s_ansible_mixin.check_library_version()
|
||||||
k8s_copy = K8SCopyToPod(module, client.client)
|
|
||||||
else:
|
k8s_ansible_mixin.module = module
|
||||||
k8s_copy = K8SCopyFromPod(module, client.client)
|
k8s_ansible_mixin.argspec = module.argument_spec
|
||||||
|
k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
|
||||||
|
k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
|
||||||
|
k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
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()
|
k8s_copy.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json("Failed to copy object due to: {0}".format(to_native(e)))
|
module.fail_json("Failed to copy object due to: {0}".format(to_native(e)))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
module_class=AnsibleModule,
|
argument_spec['namespace'] = {'type': 'str', 'required': True}
|
||||||
argument_spec=argspec(),
|
argument_spec['pod'] = {'type': 'str', 'required': True}
|
||||||
check_pyyaml=False,
|
argument_spec['container'] = {}
|
||||||
mutually_exclusive=[("local_path", "content")],
|
argument_spec['remote_path'] = {'type': 'path', 'required': True}
|
||||||
required_if=[("state", "from_pod", ["local_path"])],
|
argument_spec['local_path'] = {'type': 'path'}
|
||||||
required_one_of=[["local_path", "content"]],
|
argument_spec['content'] = {'type': 'str'}
|
||||||
supports_check_mode=True,
|
argument_spec['state'] = {'type': 'str', 'default': 'to_pod', 'choices': ['to_pod', 'from_pod']}
|
||||||
)
|
argument_spec['no_preserve'] = {'type': 'bool', 'default': False}
|
||||||
|
|
||||||
try:
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
execute_module(module)
|
mutually_exclusive=[('local_path', 'content')],
|
||||||
except CoreException as e:
|
required_if=[('state', 'from_pod', ['local_path'])],
|
||||||
module.fail_from_exception(e)
|
required_one_of=[['local_path', 'content']],
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
execute_module(module)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s_drain
|
module: k8s_drain
|
||||||
|
|
||||||
@@ -64,12 +64,6 @@ options:
|
|||||||
- Ignore DaemonSet-managed pods.
|
- Ignore DaemonSet-managed pods.
|
||||||
type: bool
|
type: bool
|
||||||
default: False
|
default: False
|
||||||
delete_emptydir_data:
|
|
||||||
description:
|
|
||||||
- Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
version_added: 2.3.0
|
|
||||||
disable_eviction:
|
disable_eviction:
|
||||||
description:
|
description:
|
||||||
- Forces drain to use delete rather than evict.
|
- Forces drain to use delete rather than evict.
|
||||||
@@ -89,9 +83,9 @@ options:
|
|||||||
requirements:
|
requirements:
|
||||||
- python >= 3.6
|
- python >= 3.6
|
||||||
- kubernetes >= 12.0.0
|
- 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.
|
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
|
||||||
kubernetes.core.k8s_drain:
|
kubernetes.core.k8s_drain:
|
||||||
state: drain
|
state: drain
|
||||||
@@ -115,16 +109,15 @@ EXAMPLES = r"""
|
|||||||
state: cordon
|
state: cordon
|
||||||
name: foo
|
name: foo
|
||||||
|
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- The node status and the number of pods deleted.
|
- The node status and the number of pods deleted.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -136,21 +129,11 @@ from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule impo
|
|||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC,
|
||||||
)
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.client.api import core_v1_api
|
from kubernetes.client.api import core_v1_api
|
||||||
from kubernetes.client.models import V1DeleteOptions, V1ObjectMeta
|
from kubernetes.client.models import V1DeleteOptions
|
||||||
from kubernetes.client.exceptions import ApiException
|
from kubernetes.client.exceptions import ApiException
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# ImportError are managed by the common module already.
|
# ImportError are managed by the common module already.
|
||||||
@@ -171,7 +154,7 @@ except ImportError:
|
|||||||
HAS_EVICTION_API = False
|
HAS_EVICTION_API = False
|
||||||
|
|
||||||
|
|
||||||
def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
def filter_pods(pods, force, ignore_daemonset):
|
||||||
k8s_kind_mirror = "kubernetes.io/config.mirror"
|
k8s_kind_mirror = "kubernetes.io/config.mirror"
|
||||||
daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], []
|
daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], []
|
||||||
for pod in pods:
|
for pod in pods:
|
||||||
@@ -181,11 +164,12 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Any finished pod can be deleted
|
# 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))
|
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Pod with local storage cannot be deleted
|
# Pod with local storage cannot be deleted
|
||||||
|
# TODO: support new option delete-emptydatadir in order to allow deletion of such pod
|
||||||
if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes):
|
if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes):
|
||||||
localStorage.append((pod.metadata.namespace, pod.metadata.name))
|
localStorage.append((pod.metadata.namespace, pod.metadata.name))
|
||||||
continue
|
continue
|
||||||
@@ -203,68 +187,66 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
|
|
||||||
warnings, errors = [], []
|
warnings, errors = [], []
|
||||||
if unmanaged:
|
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:
|
if not force:
|
||||||
errors.append(
|
errors.append("cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
||||||
"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(pod_names))
|
||||||
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(
|
|
||||||
pod_names
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# Pod not managed will be deleted as 'force' is true
|
# Pod not managed will be deleted as 'force' is true
|
||||||
warnings.append(
|
warnings.append("Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(pod_names))
|
||||||
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
|
|
||||||
pod_names
|
|
||||||
)
|
|
||||||
)
|
|
||||||
to_delete += unmanaged
|
to_delete += unmanaged
|
||||||
|
|
||||||
# mirror pods warning
|
# mirror pods warning
|
||||||
if mirror:
|
if mirror:
|
||||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in mirror])
|
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in mirror])
|
||||||
warnings.append(
|
warnings.append("cannot delete mirror Pods using API server: {0}.".format(pod_names))
|
||||||
"cannot delete mirror Pods using API server: {0}.".format(pod_names)
|
|
||||||
)
|
|
||||||
|
|
||||||
# local storage
|
# local storage
|
||||||
if localStorage:
|
if localStorage:
|
||||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage])
|
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage])
|
||||||
if not delete_emptydir_data:
|
errors.append("cannot delete Pods with local storage: {0}.".format(pod_names))
|
||||||
errors.append(
|
|
||||||
"cannot delete Pods with local storage: {0}.".format(pod_names)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
warnings.append("Deleting Pods with local storage: {0}.".format(pod_names))
|
|
||||||
for pod in localStorage:
|
|
||||||
to_delete.append((pod[0], pod[1]))
|
|
||||||
|
|
||||||
# DaemonSet managed Pods
|
# DaemonSet managed Pods
|
||||||
if daemonSet:
|
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:
|
if not ignore_daemonset:
|
||||||
errors.append(
|
errors.append("cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(pod_names))
|
||||||
"cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(
|
|
||||||
pod_names
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
||||||
return to_delete, warnings, errors
|
return to_delete, warnings, errors
|
||||||
|
|
||||||
|
|
||||||
class K8sDrainAnsible(object):
|
class K8sDrainAnsible(object):
|
||||||
def __init__(self, module, client):
|
|
||||||
|
def __init__(self, module):
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin, get_api_client)
|
||||||
|
|
||||||
self._module = module
|
self._module = module
|
||||||
self._api_instance = core_v1_api.CoreV1Api(client.client)
|
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
|
self._k8s_ansible_mixin.client = get_api_client(module=self._module)
|
||||||
|
|
||||||
|
self._k8s_ansible_mixin.module = self._module
|
||||||
|
self._k8s_ansible_mixin.argspec = self._module.argument_spec
|
||||||
|
self._k8s_ansible_mixin.check_mode = self._module.check_mode
|
||||||
|
self._k8s_ansible_mixin.params = self._module.params
|
||||||
|
self._k8s_ansible_mixin.fail_json = self._module.fail_json
|
||||||
|
self._k8s_ansible_mixin.fail = self._module.fail_json
|
||||||
|
self._k8s_ansible_mixin.exit_json = self._module.exit_json
|
||||||
|
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._k8s_ansible_mixin.check_library_version()
|
||||||
|
|
||||||
# delete options
|
# delete options
|
||||||
self._drain_options = module.params.get("delete_options", {})
|
self._drain_options = module.params.get('delete_options', {})
|
||||||
self._delete_options = None
|
self._delete_options = None
|
||||||
if self._drain_options.get("terminate_grace_period"):
|
if self._drain_options.get('terminate_grace_period'):
|
||||||
self._delete_options = V1DeleteOptions(
|
self._delete_options = {}
|
||||||
grace_period_seconds=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
|
self._changed = False
|
||||||
|
|
||||||
@@ -280,17 +262,13 @@ class K8sDrainAnsible(object):
|
|||||||
if not pod:
|
if not pod:
|
||||||
pod = pods.pop()
|
pod = pods.pop()
|
||||||
try:
|
try:
|
||||||
response = self._api_instance.read_namespaced_pod(
|
response = self._api_instance.read_namespaced_pod(namespace=pod[0], name=pod[1])
|
||||||
namespace=pod[0], name=pod[1]
|
|
||||||
)
|
|
||||||
if not response:
|
if not response:
|
||||||
pod = None
|
pod = None
|
||||||
time.sleep(wait_sleep)
|
time.sleep(wait_sleep)
|
||||||
except ApiException as exc:
|
except ApiException as exc:
|
||||||
if exc.reason != "Not Found":
|
if exc.reason != "Not Found":
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Exception raised: {0}".format(exc.reason))
|
||||||
msg="Exception raised: {0}".format(exc.reason)
|
|
||||||
)
|
|
||||||
pod = None
|
pod = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._module.fail_json(msg="Exception raised: {0}".format(to_native(e)))
|
self._module.fail_json(msg="Exception raised: {0}".format(to_native(e)))
|
||||||
@@ -300,49 +278,39 @@ class K8sDrainAnsible(object):
|
|||||||
|
|
||||||
def evict_pods(self, pods):
|
def evict_pods(self, pods):
|
||||||
for namespace, name in pods:
|
for namespace, name in pods:
|
||||||
|
definition = {
|
||||||
|
'metadata': {
|
||||||
|
'name': name,
|
||||||
|
'namespace': namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self._delete_options:
|
||||||
|
definition.update({'delete_options': self._delete_options})
|
||||||
try:
|
try:
|
||||||
if self._drain_options.get("disable_eviction"):
|
if self._drain_options.get('disable_eviction'):
|
||||||
self._api_instance.delete_namespaced_pod(
|
body = V1DeleteOptions(**definition)
|
||||||
name=name, namespace=namespace, body=self._delete_options
|
self._api_instance.delete_namespaced_pod(name=name, namespace=namespace, body=body)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
body = v1_eviction(
|
body = v1_eviction(**definition)
|
||||||
delete_options=self._delete_options,
|
|
||||||
metadata=V1ObjectMeta(name=name, namespace=namespace),
|
|
||||||
)
|
|
||||||
self._api_instance.create_namespaced_pod_eviction(
|
self._api_instance.create_namespaced_pod_eviction(
|
||||||
name=name, namespace=namespace, body=body
|
name=name, namespace=namespace, body=body
|
||||||
)
|
)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
except ApiException as exc:
|
except ApiException as exc:
|
||||||
if exc.reason != "Not Found":
|
if exc.reason != "Not Found":
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, exc.reason))
|
||||||
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
|
||||||
namespace, name, exc.reason
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, to_native(exc)))
|
||||||
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
|
||||||
namespace, name, to_native(exc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete_or_evict_pods(self, node_unschedulable):
|
def delete_or_evict_pods(self, node_unschedulable):
|
||||||
# Mark node as unschedulable
|
# Mark node as unschedulable
|
||||||
result = []
|
result = []
|
||||||
if not node_unschedulable:
|
if not node_unschedulable:
|
||||||
self.patch_node(unschedulable=True)
|
self.patch_node(unschedulable=True)
|
||||||
result.append(
|
result.append("node {0} marked unschedulable.".format(self._module.params.get('name')))
|
||||||
"node {0} marked unschedulable.".format(self._module.params.get("name"))
|
|
||||||
)
|
|
||||||
self._changed = True
|
self._changed = True
|
||||||
else:
|
else:
|
||||||
result.append(
|
result.append("node {0} already marked unschedulable.".format(self._module.params.get('name')))
|
||||||
"node {0} already marked unschedulable.".format(
|
|
||||||
self._module.params.get("name")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _revert_node_patch():
|
def _revert_node_patch():
|
||||||
if self._changed:
|
if self._changed:
|
||||||
@@ -350,112 +318,77 @@ class K8sDrainAnsible(object):
|
|||||||
self.patch_node(unschedulable=False)
|
self.patch_node(unschedulable=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
field_selector = "spec.nodeName={name}".format(
|
field_selector = "spec.nodeName={name}".format(name=self._module.params.get('name'))
|
||||||
name=self._module.params.get("name")
|
pod_list = self._api_instance.list_pod_for_all_namespaces(field_selector=field_selector)
|
||||||
)
|
|
||||||
pod_list = self._api_instance.list_pod_for_all_namespaces(
|
|
||||||
field_selector=field_selector
|
|
||||||
)
|
|
||||||
# Filter pods
|
# Filter pods
|
||||||
force = self._drain_options.get("force", False)
|
force = self._drain_options.get('force', False)
|
||||||
ignore_daemonset = self._drain_options.get("ignore_daemonsets", False)
|
ignore_daemonset = self._drain_options.get('ignore_daemonsets', False)
|
||||||
delete_emptydir_data = self._drain_options.get(
|
pods, warnings, errors = filter_pods(pod_list.items, force, ignore_daemonset)
|
||||||
"delete_emptydir_data", False
|
|
||||||
)
|
|
||||||
pods, warnings, errors = filter_pods(
|
|
||||||
pod_list.items, force, ignore_daemonset, delete_emptydir_data
|
|
||||||
)
|
|
||||||
if errors:
|
if errors:
|
||||||
_revert_node_patch()
|
_revert_node_patch()
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Pod deletion errors: {0}".format(" ".join(errors)))
|
||||||
msg="Pod deletion errors: {0}".format(" ".join(errors))
|
|
||||||
)
|
|
||||||
except ApiException as exc:
|
except ApiException as exc:
|
||||||
if exc.reason != "Not Found":
|
if exc.reason != "Not Found":
|
||||||
_revert_node_patch()
|
_revert_node_patch()
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to list pod from node {name} due to: {reason}".format(
|
||||||
msg="Failed to list pod from node {name} due to: {reason}".format(
|
name=self._module.params.get('name'), reason=exc.reason), status=exc.status)
|
||||||
name=self._module.params.get("name"), reason=exc.reason
|
|
||||||
),
|
|
||||||
status=exc.status,
|
|
||||||
)
|
|
||||||
pods = []
|
pods = []
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_revert_node_patch()
|
_revert_node_patch()
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to list pod from node {name} due to: {error}".format(
|
||||||
msg="Failed to list pod from node {name} due to: {error}".format(
|
name=self._module.params.get('name'), error=to_native(exc)))
|
||||||
name=self._module.params.get("name"), error=to_native(exc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete Pods
|
# Delete Pods
|
||||||
if pods:
|
if pods:
|
||||||
self.evict_pods(pods)
|
self.evict_pods(pods)
|
||||||
number_pod = len(pods)
|
number_pod = len(pods)
|
||||||
if self._drain_options.get("wait_timeout") is not None:
|
if self._drain_options.get('wait_timeout') is not None:
|
||||||
warn = self.wait_for_pod_deletion(
|
warn = self.wait_for_pod_deletion(pods,
|
||||||
pods,
|
self._drain_options.get('wait_timeout'),
|
||||||
self._drain_options.get("wait_timeout"),
|
self._drain_options.get('wait_sleep'))
|
||||||
self._drain_options.get("wait_sleep"),
|
|
||||||
)
|
|
||||||
if warn:
|
if warn:
|
||||||
warnings.append(warn)
|
warnings.append(warn)
|
||||||
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
||||||
if warnings:
|
if warnings:
|
||||||
return dict(result=" ".join(result), warnings=warnings)
|
return dict(result=' '.join(result), warnings=warnings)
|
||||||
return dict(result=" ".join(result))
|
return dict(result=' '.join(result))
|
||||||
|
|
||||||
def patch_node(self, unschedulable):
|
def patch_node(self, unschedulable):
|
||||||
|
|
||||||
body = {"spec": {"unschedulable": unschedulable}}
|
body = {
|
||||||
|
'spec': {'unschedulable': unschedulable}
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
self._api_instance.patch_node(
|
self._api_instance.patch_node(name=self._module.params.get('name'), body=body)
|
||||||
name=self._module.params.get("name"), body=body
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to patch node due to: {0}".format(to_native(exc)))
|
||||||
msg="Failed to patch node due to: {0}".format(to_native(exc))
|
|
||||||
)
|
|
||||||
|
|
||||||
def execute_module(self):
|
def execute_module(self):
|
||||||
|
|
||||||
state = self._module.params.get("state")
|
state = self._module.params.get('state')
|
||||||
name = self._module.params.get("name")
|
name = self._module.params.get('name')
|
||||||
try:
|
try:
|
||||||
node = self._api_instance.read_node(name=name)
|
node = self._api_instance.read_node(name=name)
|
||||||
except ApiException as exc:
|
except ApiException as exc:
|
||||||
if exc.reason == "Not Found":
|
if exc.reason == "Not Found":
|
||||||
self._module.fail_json(msg="Node {0} not found.".format(name))
|
self._module.fail_json(msg="Node {0} not found.".format(name))
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, exc.reason), status=exc.status)
|
||||||
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
|
||||||
name, exc.reason
|
|
||||||
),
|
|
||||||
status=exc.status,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(
|
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, to_native(exc)))
|
||||||
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
|
||||||
name, to_native(exc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
if state == "cordon":
|
if state == "cordon":
|
||||||
if node.spec.unschedulable:
|
if node.spec.unschedulable:
|
||||||
self._module.exit_json(
|
self._module.exit_json(result="node {0} already marked unschedulable.".format(name))
|
||||||
result="node {0} already marked unschedulable.".format(name)
|
|
||||||
)
|
|
||||||
self.patch_node(unschedulable=True)
|
self.patch_node(unschedulable=True)
|
||||||
result["result"] = "node {0} marked unschedulable.".format(name)
|
result['result'] = "node {0} marked unschedulable.".format(name)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
|
|
||||||
elif state == "uncordon":
|
elif state == "uncordon":
|
||||||
if not node.spec.unschedulable:
|
if not node.spec.unschedulable:
|
||||||
self._module.exit_json(
|
self._module.exit_json(result="node {0} already marked schedulable.".format(name))
|
||||||
result="node {0} already marked schedulable.".format(name)
|
|
||||||
)
|
|
||||||
self.patch_node(unschedulable=False)
|
self.patch_node(unschedulable=False)
|
||||||
result["result"] = "node {0} marked schedulable.".format(name)
|
result['result'] = "node {0} marked schedulable.".format(name)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -474,17 +407,16 @@ def argspec():
|
|||||||
state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
|
state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
|
||||||
name=dict(required=True),
|
name=dict(required=True),
|
||||||
delete_options=dict(
|
delete_options=dict(
|
||||||
type="dict",
|
type='dict',
|
||||||
default={},
|
default={},
|
||||||
options=dict(
|
options=dict(
|
||||||
terminate_grace_period=dict(type="int"),
|
terminate_grace_period=dict(type='int'),
|
||||||
force=dict(type="bool", default=False),
|
force=dict(type='bool', default=False),
|
||||||
ignore_daemonsets=dict(type="bool", default=False),
|
ignore_daemonsets=dict(type='bool', default=False),
|
||||||
delete_emptydir_data=dict(type="bool", default=False),
|
disable_eviction=dict(type='bool', default=False),
|
||||||
disable_eviction=dict(type="bool", default=False),
|
wait_timeout=dict(type='int'),
|
||||||
wait_timeout=dict(type="int"),
|
wait_sleep=dict(type='int', default=5),
|
||||||
wait_sleep=dict(type="int", default=5),
|
)
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -492,7 +424,7 @@ def argspec():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(module_class=AnsibleModule, argument_spec=argspec())
|
module = AnsibleModule(argument_spec=argspec())
|
||||||
|
|
||||||
if not HAS_EVICTION_API:
|
if not HAS_EVICTION_API:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
@@ -501,13 +433,9 @@ def main():
|
|||||||
error=to_native(k8s_import_exception),
|
error=to_native(k8s_import_exception),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
k8s_drain = K8sDrainAnsible(module)
|
||||||
client = get_api_client(module=module)
|
k8s_drain.execute_module()
|
||||||
k8s_drain = K8sDrainAnsible(module, client.client)
|
|
||||||
k8s_drain.execute_module()
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s_exec
|
module: k8s_exec
|
||||||
|
|
||||||
@@ -40,33 +40,32 @@ options:
|
|||||||
description:
|
description:
|
||||||
- The URL of an HTTP proxy to use for the connection.
|
- The URL of an HTTP proxy to use for the connection.
|
||||||
- Can also be specified via I(K8S_AUTH_PROXY) environment variable.
|
- Can also be specified via I(K8S_AUTH_PROXY) environment variable.
|
||||||
- Please note that this module does not pick up typical proxy settings from the environment (for example, HTTP_PROXY).
|
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
|
||||||
type: str
|
type: str
|
||||||
namespace:
|
namespace:
|
||||||
description:
|
description:
|
||||||
- The pod namespace name.
|
- The pod namespace name
|
||||||
type: str
|
type: str
|
||||||
required: yes
|
required: yes
|
||||||
pod:
|
pod:
|
||||||
description:
|
description:
|
||||||
- The pod name.
|
- The pod name
|
||||||
type: str
|
type: str
|
||||||
required: yes
|
required: yes
|
||||||
container:
|
container:
|
||||||
description:
|
description:
|
||||||
- The name of the container in the pod to connect to.
|
- The name of the container in the pod to connect to.
|
||||||
- Defaults to only container if there is only one container in the pod.
|
- Defaults to only container if there is only one container in the pod.
|
||||||
- If not specified, will choose the first container from the given pod as kubectl cmdline does.
|
|
||||||
type: str
|
type: str
|
||||||
required: no
|
required: no
|
||||||
command:
|
command:
|
||||||
description:
|
description:
|
||||||
- The command to execute.
|
- The command to execute
|
||||||
type: str
|
type: str
|
||||||
required: yes
|
required: yes
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Execute a command
|
- name: Execute a command
|
||||||
kubernetes.core.k8s_exec:
|
kubernetes.core.k8s_exec:
|
||||||
namespace: myproject
|
namespace: myproject
|
||||||
@@ -85,16 +84,9 @@ EXAMPLES = r"""
|
|||||||
debug:
|
debug:
|
||||||
msg: "cmd failed"
|
msg: "cmd failed"
|
||||||
when: command_status.rc != 0
|
when: command_status.rc != 0
|
||||||
|
'''
|
||||||
|
|
||||||
- name: Specify a container name to execute the command on
|
RETURN = r'''
|
||||||
kubernetes.core.k8s_exec:
|
|
||||||
namespace: myproject
|
|
||||||
pod: busybox-test
|
|
||||||
container: manager
|
|
||||||
command: echo "hello"
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- The command object
|
- The command object
|
||||||
@@ -120,7 +112,7 @@ result:
|
|||||||
return_code:
|
return_code:
|
||||||
description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
|
description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
|
||||||
type: int
|
type: int
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import shlex
|
import shlex
|
||||||
@@ -131,27 +123,15 @@ except ImportError:
|
|||||||
# ImportError are managed by the common module already.
|
# ImportError are managed by the common module already.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleModule,
|
|
||||||
)
|
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.client.apis import core_v1_api
|
from kubernetes.client.apis import core_v1_api
|
||||||
from kubernetes.stream import stream
|
from kubernetes.stream import stream
|
||||||
from kubernetes.client.exceptions import ApiException
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# ImportError are managed by the common module already.
|
# ImportError are managed by the common module already.
|
||||||
pass
|
pass
|
||||||
@@ -159,34 +139,22 @@ except ImportError:
|
|||||||
|
|
||||||
def argspec():
|
def argspec():
|
||||||
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
spec["namespace"] = dict(type="str", required=True)
|
spec['namespace'] = dict(type='str', required=True)
|
||||||
spec["pod"] = dict(type="str", required=True)
|
spec['pod'] = dict(type='str', required=True)
|
||||||
spec["container"] = dict(type="str")
|
spec['container'] = dict(type='str')
|
||||||
spec["command"] = dict(type="str", required=True)
|
spec['command'] = dict(type='str', required=True)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, client):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
|
|
||||||
# Load kubernetes.client.Configuration
|
# Load kubernetes.client.Configuration
|
||||||
api = core_v1_api.CoreV1Api(client.client)
|
api = core_v1_api.CoreV1Api(k8s_ansible_mixin.client.client)
|
||||||
|
|
||||||
# hack because passing the container as None breaks things
|
# hack because passing the container as None breaks things
|
||||||
optional_kwargs = {}
|
optional_kwargs = {}
|
||||||
if module.params.get("container"):
|
if module.params.get('container'):
|
||||||
optional_kwargs["container"] = module.params["container"]
|
optional_kwargs['container'] = module.params['container']
|
||||||
else:
|
|
||||||
# default to the first container available on pod
|
|
||||||
resp = None
|
|
||||||
try:
|
|
||||||
resp = api.read_namespaced_pod(
|
|
||||||
name=module.params["pod"], namespace=module.params["namespace"]
|
|
||||||
)
|
|
||||||
except ApiException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if resp and len(resp.spec.containers) >= 1:
|
|
||||||
optional_kwargs["container"] = resp.spec.containers[0].name
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = stream(
|
resp = stream(
|
||||||
api.connect_get_namespaced_pod_exec,
|
api.connect_get_namespaced_pod_exec,
|
||||||
@@ -197,14 +165,10 @@ def execute_module(module, client):
|
|||||||
stderr=True,
|
stderr=True,
|
||||||
stdin=False,
|
stdin=False,
|
||||||
tty=False,
|
tty=False,
|
||||||
_preload_content=False,
|
_preload_content=False, **optional_kwargs)
|
||||||
**optional_kwargs
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(
|
module.fail_json(msg="Failed to execute on pod %s"
|
||||||
msg="Failed to execute on pod %s"
|
" due to : %s" % (module.params.get('pod'), to_native(e)))
|
||||||
" due to : %s" % (module.params.get("pod"), to_native(e))
|
|
||||||
)
|
|
||||||
stdout, stderr, rc = [], [], 0
|
stdout, stderr, rc = [], [], 0
|
||||||
while resp.is_open():
|
while resp.is_open():
|
||||||
resp.update(timeout=1)
|
resp.update(timeout=1)
|
||||||
@@ -214,41 +178,34 @@ def execute_module(module, client):
|
|||||||
stderr.append(resp.read_stderr())
|
stderr.append(resp.read_stderr())
|
||||||
err = resp.read_channel(3)
|
err = resp.read_channel(3)
|
||||||
err = yaml.safe_load(err)
|
err = yaml.safe_load(err)
|
||||||
if err["status"] == "Success":
|
if err['status'] == 'Success':
|
||||||
rc = 0
|
rc = 0
|
||||||
else:
|
else:
|
||||||
rc = int(err["details"]["causes"][0]["message"])
|
rc = int(err['details']['causes'][0]['message'])
|
||||||
|
|
||||||
module.deprecate(
|
module.deprecate("The 'return_code' return key is deprecated. Please use 'rc' instead.", version="4.0.0", collection_name="kubernetes.core")
|
||||||
"The 'return_code' return key is being renamed to 'rc'. "
|
|
||||||
"Both keys are being returned for now to allow users to migrate their automation.",
|
|
||||||
version="4.0.0",
|
|
||||||
collection_name="kubernetes.core",
|
|
||||||
)
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
# Some command might change environment, but ultimately failing at end
|
# Some command might change environment, but ultimately failing at end
|
||||||
changed=True,
|
changed=True,
|
||||||
stdout="".join(stdout),
|
stdout="".join(stdout),
|
||||||
stderr="".join(stderr),
|
stderr="".join(stderr),
|
||||||
rc=rc,
|
rc=rc,
|
||||||
return_code=rc,
|
return_code=rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(
|
||||||
module_class=AnsibleModule,
|
|
||||||
check_pyyaml=False,
|
|
||||||
argument_spec=argspec(),
|
argument_spec=argspec(),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin, get_api_client)
|
||||||
|
|
||||||
try:
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
client = get_api_client(module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, client.client)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
module: k8s_info
|
module: k8s_info
|
||||||
|
|
||||||
short_description: Describe Kubernetes (K8s) objects
|
short_description: Describe Kubernetes (K8s) objects
|
||||||
@@ -52,9 +52,9 @@ requirements:
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Get an existing Service object
|
- name: Get an existing Service object
|
||||||
kubernetes.core.k8s_info:
|
kubernetes.core.k8s_info:
|
||||||
api_version: v1
|
api_version: v1
|
||||||
@@ -109,9 +109,9 @@ EXAMPLES = r"""
|
|||||||
namespace: default
|
namespace: default
|
||||||
wait_sleep: 10
|
wait_sleep: 10
|
||||||
wait_timeout: 360
|
wait_timeout: 360
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
api_found:
|
api_found:
|
||||||
description:
|
description:
|
||||||
- Whether the specified api_version and kind were successfully mapped to an existing API on the targeted cluster.
|
- Whether the specified api_version and kind were successfully mapped to an existing API on the targeted cluster.
|
||||||
@@ -144,33 +144,16 @@ resources:
|
|||||||
description: Current status details for the object.
|
description: Current status details for the object.
|
||||||
returned: success
|
returned: success
|
||||||
type: dict
|
type: dict
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
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.args_common import (
|
|
||||||
AUTH_ARG_SPEC,
|
|
||||||
WAIT_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, svc):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
facts = svc.find(
|
facts = k8s_ansible_mixin.kubernetes_facts(
|
||||||
module.params["kind"],
|
module.params["kind"],
|
||||||
module.params["api_version"],
|
module.params["api_version"],
|
||||||
name=module.params["name"],
|
name=module.params["name"],
|
||||||
@@ -191,27 +174,29 @@ def argspec():
|
|||||||
args.update(
|
args.update(
|
||||||
dict(
|
dict(
|
||||||
kind=dict(required=True),
|
kind=dict(required=True),
|
||||||
api_version=dict(default="v1", aliases=["api", "version"]),
|
api_version=dict(default='v1', aliases=['api', 'version']),
|
||||||
name=dict(),
|
name=dict(),
|
||||||
namespace=dict(),
|
namespace=dict(),
|
||||||
label_selectors=dict(type="list", elements="str", default=[]),
|
label_selectors=dict(type='list', elements='str', default=[]),
|
||||||
field_selectors=dict(type="list", elements="str", default=[]),
|
field_selectors=dict(type='list', elements='str', default=[]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
)
|
K8sAnsibleMixin, get_api_client)
|
||||||
try:
|
|
||||||
client = get_api_client(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
svc = K8sService(client, module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, svc)
|
k8s_ansible_mixin.fail_json = module.fail_json
|
||||||
except CoreException as e:
|
k8s_ansible_mixin.fail = module.fail_json
|
||||||
module.fail_from_exception(e)
|
k8s_ansible_mixin.exit_json = module.exit_json
|
||||||
|
k8s_ansible_mixin.warn = module.warn
|
||||||
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
module: k8s_json_patch
|
module: k8s_json_patch
|
||||||
|
|
||||||
short_description: Apply JSON patch operations to existing objects
|
short_description: Apply JSON patch operations to existing objects
|
||||||
@@ -66,9 +66,9 @@ requirements:
|
|||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
- "jsonpatch"
|
- "jsonpatch"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Apply multiple patch operations to an existing Pod
|
- name: Apply multiple patch operations to an existing Pod
|
||||||
kubernetes.core.k8s_json_patch:
|
kubernetes.core.k8s_json_patch:
|
||||||
kind: Pod
|
kind: Pod
|
||||||
@@ -79,11 +79,11 @@ EXAMPLES = r"""
|
|||||||
path: /metadata/labels/app
|
path: /metadata/labels/app
|
||||||
value: myapp
|
value: myapp
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /spec/containers/0/image
|
patch: /spec/containers/0/image
|
||||||
value: nginx
|
value: nginx
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
result:
|
result:
|
||||||
description: The modified object.
|
description: The modified object.
|
||||||
returned: success
|
returned: success
|
||||||
@@ -122,36 +122,17 @@ error:
|
|||||||
"msg": "Failed to import the required Python library (jsonpatch) ...",
|
"msg": "Failed to import the required Python library (jsonpatch) ...",
|
||||||
"exception": "Traceback (most recent call last): ..."
|
"exception": "Traceback (most recent call last): ..."
|
||||||
}
|
}
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
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 (
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
get_api_client, K8sAnsibleMixin)
|
||||||
AUTH_ARG_SPEC,
|
|
||||||
WAIT_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
diff_objects,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
|
|
||||||
get_waiter,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||||
@@ -162,7 +143,6 @@ except ImportError:
|
|||||||
JSON_PATCH_IMPORT_ERR = None
|
JSON_PATCH_IMPORT_ERR = None
|
||||||
try:
|
try:
|
||||||
import jsonpatch
|
import jsonpatch
|
||||||
|
|
||||||
HAS_JSON_PATCH = True
|
HAS_JSON_PATCH = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_JSON_PATCH = False
|
HAS_JSON_PATCH = False
|
||||||
@@ -170,18 +150,33 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
JSON_PATCH_ARGS = {
|
JSON_PATCH_ARGS = {
|
||||||
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
'api_version': {
|
||||||
"kind": {"type": "str", "required": True},
|
'default': 'v1',
|
||||||
"namespace": {"type": "str"},
|
'aliases': ['api', 'version'],
|
||||||
"name": {"type": "str", "required": True},
|
},
|
||||||
"patch": {"type": "list", "required": True, "elements": "dict"},
|
"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):
|
def json_patch(existing, patch):
|
||||||
if not HAS_JSON_PATCH:
|
if not HAS_JSON_PATCH:
|
||||||
error = {
|
error = {
|
||||||
"msg": missing_required_lib("jsonpatch"),
|
"msg": missing_required_lib('jsonpatch'),
|
||||||
"exception": JSON_PATCH_IMPORT_ERR,
|
"exception": JSON_PATCH_IMPORT_ERR,
|
||||||
}
|
}
|
||||||
return None, error
|
return None, error
|
||||||
@@ -190,14 +185,20 @@ def json_patch(existing, patch):
|
|||||||
patched = patch.apply(existing)
|
patched = patch.apply(existing)
|
||||||
return patched, None
|
return patched, None
|
||||||
except jsonpatch.InvalidJsonPatch as e:
|
except jsonpatch.InvalidJsonPatch as e:
|
||||||
error = {"msg": "Invalid JSON patch", "exception": e}
|
error = {
|
||||||
|
"msg": "Invalid JSON patch",
|
||||||
|
"exception": e
|
||||||
|
}
|
||||||
return None, error
|
return None, error
|
||||||
except jsonpatch.JsonPatchConflict as e:
|
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
|
return None, error
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, client):
|
def execute_module(k8s_module, module):
|
||||||
kind = module.params.get("kind")
|
kind = module.params.get("kind")
|
||||||
api_version = module.params.get("api_version")
|
api_version = module.params.get("api_version")
|
||||||
name = module.params.get("name")
|
name = module.params.get("name")
|
||||||
@@ -208,66 +209,50 @@ def execute_module(module, client):
|
|||||||
wait_sleep = module.params.get("wait_sleep")
|
wait_sleep = module.params.get("wait_sleep")
|
||||||
wait_timeout = module.params.get("wait_timeout")
|
wait_timeout = module.params.get("wait_timeout")
|
||||||
wait_condition = None
|
wait_condition = None
|
||||||
if module.params.get("wait_condition") and module.params.get("wait_condition").get(
|
if module.params.get("wait_condition") and module.params.get("wait_condition").get("type"):
|
||||||
"type"
|
wait_condition = module.params['wait_condition']
|
||||||
):
|
# definition is needed for wait
|
||||||
wait_condition = module.params["wait_condition"]
|
definition = {
|
||||||
|
"kind": kind,
|
||||||
|
"metadata": {
|
||||||
|
"name": name,
|
||||||
|
"namespace": namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def build_error_msg(kind, name, msg):
|
def build_error_msg(kind, name, msg):
|
||||||
return "%s %s: %s" % (kind, name, msg)
|
return "%s %s: %s" % (kind, name, msg)
|
||||||
|
|
||||||
resource = client.resource(kind, api_version)
|
resource = k8s_module.find_resource(kind, api_version, fail=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
existing = client.get(resource, name=name, namespace=namespace)
|
existing = resource.get(name=name, namespace=namespace)
|
||||||
except DynamicApiError as exc:
|
except DynamicApiError as exc:
|
||||||
msg = "Failed to retrieve requested object: {0}".format(exc.body)
|
msg = 'Failed to retrieve requested object: {0}'.format(exc.body)
|
||||||
module.fail_json(
|
module.fail_json(msg=build_error_msg(kind, name, msg), error=exc.status, status=exc.status, reason=exc.reason)
|
||||||
msg=build_error_msg(kind, name, msg),
|
|
||||||
error=exc.status,
|
|
||||||
status=exc.status,
|
|
||||||
reason=exc.reason,
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
msg = "Failed to retrieve requested object: {0}".format(to_native(exc))
|
msg = 'Failed to retrieve requested object: {0}'.format(to_native(exc))
|
||||||
module.fail_json(
|
module.fail_json(msg=build_error_msg(kind, name, msg), error='', status='', reason='')
|
||||||
msg=build_error_msg(kind, name, msg), error="", status="", reason=""
|
|
||||||
)
|
|
||||||
|
|
||||||
if module.check_mode and not client.dry_run:
|
if module.check_mode:
|
||||||
obj, error = json_patch(existing.to_dict(), patch)
|
obj, error = json_patch(existing.to_dict(), patch)
|
||||||
if error:
|
if error:
|
||||||
module.fail_json(**error)
|
module.fail_json(**error)
|
||||||
else:
|
else:
|
||||||
params = {}
|
|
||||||
if module.check_mode:
|
|
||||||
params["dry_run"] = "All"
|
|
||||||
try:
|
try:
|
||||||
obj = client.patch(
|
obj = resource.patch(patch, name=name, namespace=namespace, content_type="application/json-patch+json").to_dict()
|
||||||
resource,
|
|
||||||
patch,
|
|
||||||
name=name,
|
|
||||||
namespace=namespace,
|
|
||||||
content_type="application/json-patch+json",
|
|
||||||
**params
|
|
||||||
).to_dict()
|
|
||||||
except DynamicApiError as exc:
|
except DynamicApiError as exc:
|
||||||
msg = "Failed to patch existing object: {0}".format(exc.body)
|
msg = "Failed to patch existing object: {0}".format(exc.body)
|
||||||
module.fail_json(
|
module.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
|
||||||
msg=msg, error=exc.status, status=exc.status, reason=exc.reason
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
msg = "Failed to patch existing object: {0}".format(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
|
success = True
|
||||||
result = {"result": obj}
|
result = {"result": obj}
|
||||||
if wait and not module.check_mode:
|
if wait and not module.check_mode:
|
||||||
waiter = get_waiter(client, resource, condition=wait_condition)
|
success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||||
success, result["result"], result["duration"] = waiter.wait(
|
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
||||||
wait_timeout, wait_sleep, name, namespace
|
|
||||||
)
|
|
||||||
match, diffs = diff_objects(existing.to_dict(), obj)
|
|
||||||
result["changed"] = not match
|
result["changed"] = not match
|
||||||
if module._diff:
|
if module._diff:
|
||||||
result["diff"] = diffs
|
result["diff"] = diffs
|
||||||
@@ -283,14 +268,13 @@ def main():
|
|||||||
args = copy.deepcopy(AUTH_ARG_SPEC)
|
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
args.update(copy.deepcopy(WAIT_ARG_SPEC))
|
args.update(copy.deepcopy(WAIT_ARG_SPEC))
|
||||||
args.update(JSON_PATCH_ARGS)
|
args.update(JSON_PATCH_ARGS)
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=args, supports_check_mode=True)
|
||||||
module_class=AnsibleModule, argument_spec=args, supports_check_mode=True
|
k8s_module = K8sAnsibleMixin(module)
|
||||||
)
|
k8s_module.params = module.params
|
||||||
try:
|
k8s_module.check_library_version()
|
||||||
client = get_api_client(module)
|
client = get_api_client(module)
|
||||||
execute_module(module, client)
|
k8s_module.client = client
|
||||||
except CoreException as e:
|
execute_module(k8s_module, module)
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
module: k8s_log
|
module: k8s_log
|
||||||
|
|
||||||
short_description: Fetch logs from Kubernetes resources
|
short_description: Fetch logs from Kubernetes resources
|
||||||
@@ -51,8 +51,7 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Use to specify the container within a pod to grab the log from.
|
- Use to specify the container within a pod to grab the log from.
|
||||||
- If there is only one container, this will default to that container.
|
- If there is only one container, this will default to that container.
|
||||||
- If there is more than one container, this option is required or set I(all_containers) to C(true).
|
- If there is more than one container, this option is required.
|
||||||
- mutually exclusive with C(all_containers).
|
|
||||||
required: no
|
required: no
|
||||||
type: str
|
type: str
|
||||||
since_seconds:
|
since_seconds:
|
||||||
@@ -61,33 +60,14 @@ options:
|
|||||||
required: no
|
required: no
|
||||||
type: str
|
type: str
|
||||||
version_added: '2.2.0'
|
version_added: '2.2.0'
|
||||||
previous:
|
|
||||||
description:
|
|
||||||
- If C(true), print the logs for the previous instance of the container in a pod if it exists.
|
|
||||||
required: no
|
|
||||||
type: bool
|
|
||||||
default: False
|
|
||||||
version_added: '2.4.0'
|
|
||||||
tail_lines:
|
|
||||||
description:
|
|
||||||
- A number of lines from the end of the logs to retrieve.
|
|
||||||
required: no
|
|
||||||
type: int
|
|
||||||
version_added: '2.4.0'
|
|
||||||
all_containers:
|
|
||||||
description:
|
|
||||||
- If set to C(true), retrieve all containers' logs in the pod(s).
|
|
||||||
- mutually exclusive with C(container).
|
|
||||||
type: bool
|
|
||||||
version_added: '2.4.0'
|
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Get a log from a Pod
|
- name: Get a log from a Pod
|
||||||
kubernetes.core.k8s_log:
|
kubernetes.core.k8s_log:
|
||||||
name: example-1
|
name: example-1
|
||||||
@@ -119,18 +99,10 @@ EXAMPLES = r"""
|
|||||||
kind: DeploymentConfig
|
kind: DeploymentConfig
|
||||||
namespace: testing
|
namespace: testing
|
||||||
name: example
|
name: example
|
||||||
tail_lines: 100
|
|
||||||
register: log
|
register: log
|
||||||
|
'''
|
||||||
|
|
||||||
# This will get the logs from all containers in Pod
|
RETURN = r'''
|
||||||
- name: Get the logs from all containers in pod
|
|
||||||
kubernetes.core.k8s_log:
|
|
||||||
namespace: testing
|
|
||||||
name: some-pod
|
|
||||||
all_containers: true
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
log:
|
log:
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
@@ -141,37 +113,15 @@ log_lines:
|
|||||||
description:
|
description:
|
||||||
- The log of the object, split on newlines
|
- The log of the object, split on newlines
|
||||||
returned: success
|
returned: success
|
||||||
"""
|
'''
|
||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
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.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||||
from kubernetes.client.exceptions import ApiException
|
|
||||||
except ImportError:
|
|
||||||
# ImportError are managed by the common module already.
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
def argspec():
|
||||||
@@ -179,184 +129,110 @@ def argspec():
|
|||||||
args.update(NAME_ARG_SPEC)
|
args.update(NAME_ARG_SPEC)
|
||||||
args.update(
|
args.update(
|
||||||
dict(
|
dict(
|
||||||
kind=dict(type="str", default="Pod"),
|
kind=dict(type='str', default='Pod'),
|
||||||
container=dict(),
|
container=dict(),
|
||||||
since_seconds=dict(),
|
since_seconds=dict(),
|
||||||
label_selectors=dict(type="list", elements="str", default=[]),
|
label_selectors=dict(type='list', elements='str', default=[]),
|
||||||
previous=dict(type="bool", default=False),
|
|
||||||
tail_lines=dict(type="int"),
|
|
||||||
all_containers=dict(type="bool"),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def get_exception_message(exc):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
try:
|
name = module.params.get('name')
|
||||||
d = json.loads(exc.body.decode("utf8"))
|
namespace = module.params.get('namespace')
|
||||||
return d["message"]
|
label_selector = ','.join(module.params.get('label_selectors', {}))
|
||||||
except Exception:
|
|
||||||
return exc
|
|
||||||
|
|
||||||
|
|
||||||
def list_containers_in_pod(svc, resource, namespace, name):
|
|
||||||
try:
|
|
||||||
result = svc.client.get(resource, name=name, namespace=namespace)
|
|
||||||
containers = [
|
|
||||||
c["name"] for c in result.to_dict()["status"]["containerStatuses"]
|
|
||||||
]
|
|
||||||
return containers
|
|
||||||
except Exception as exc:
|
|
||||||
raise CoreException(
|
|
||||||
"Unable to retrieve log from Pod due to: {0}".format(
|
|
||||||
get_exception_message(exc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_module(svc, params):
|
|
||||||
name = params.get("name")
|
|
||||||
namespace = params.get("namespace")
|
|
||||||
label_selector = ",".join(params.get("label_selectors", {}))
|
|
||||||
if name and label_selector:
|
if name and label_selector:
|
||||||
raise CoreException("Only one of name or label_selectors can be provided")
|
module.fail(msg='Only one of name or label_selectors can be provided')
|
||||||
|
|
||||||
resource = svc.find_resource(params["kind"], params["api_version"], fail=True)
|
resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
|
||||||
v1_pods = svc.find_resource("Pod", "v1", 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:
|
if not name:
|
||||||
raise CoreException(
|
module.fail(msg='name must be provided for resources that do not support the log subresource')
|
||||||
"name must be provided for resources that do not support the log subresource"
|
|
||||||
)
|
|
||||||
instance = resource.get(name=name, namespace=namespace)
|
instance = resource.get(name=name, namespace=namespace)
|
||||||
label_selector = ",".join(extract_selectors(instance))
|
label_selector = ','.join(extract_selectors(module, instance))
|
||||||
resource = v1_pods
|
resource = v1_pods
|
||||||
|
|
||||||
if label_selector:
|
if label_selector:
|
||||||
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
|
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
|
||||||
if not instances.items:
|
if not instances.items:
|
||||||
raise CoreException(
|
module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
|
||||||
"No pods in namespace {0} matched selector {1}".format(
|
|
||||||
namespace, label_selector
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# This matches the behavior of kubectl when logging pods via a selector
|
# This matches the behavior of kubectl when logging pods via a selector
|
||||||
name = instances.items[0].metadata.name
|
name = instances.items[0].metadata.name
|
||||||
resource = v1_pods
|
resource = v1_pods
|
||||||
|
|
||||||
if "base" not in resource.log.urls and not name:
|
|
||||||
raise CoreException(
|
|
||||||
"name must be provided for resources that do not support namespaced base url"
|
|
||||||
)
|
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if params.get("container"):
|
if module.params.get('container'):
|
||||||
kwargs["query_params"] = {"container": params["container"]}
|
kwargs['query_params'] = dict(container=module.params['container'])
|
||||||
|
|
||||||
if params.get("since_seconds"):
|
if module.params.get('since_seconds'):
|
||||||
kwargs.setdefault("query_params", {}).update(
|
kwargs.setdefault('query_params', {}).update({'sinceSeconds': module.params['since_seconds']})
|
||||||
{"sinceSeconds": params["since_seconds"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
if params.get("previous"):
|
log = serialize_log(resource.log.get(
|
||||||
kwargs.setdefault("query_params", {}).update({"previous": params["previous"]})
|
name=name,
|
||||||
|
namespace=namespace,
|
||||||
|
serialize=False,
|
||||||
|
**kwargs
|
||||||
|
))
|
||||||
|
|
||||||
if params.get("tail_lines"):
|
module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
|
||||||
kwargs.setdefault("query_params", {}).update(
|
|
||||||
{"tailLines": params["tail_lines"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
pod_containers = [None]
|
|
||||||
if params.get("all_containers"):
|
|
||||||
pod_containers = list_containers_in_pod(svc, resource, namespace, name)
|
|
||||||
|
|
||||||
log = ""
|
|
||||||
try:
|
|
||||||
for container in pod_containers:
|
|
||||||
if container is not None:
|
|
||||||
kwargs.setdefault("query_params", {}).update({"container": container})
|
|
||||||
response = resource.log.get(
|
|
||||||
name=name, namespace=namespace, serialize=False, **kwargs
|
|
||||||
)
|
|
||||||
log += response.data.decode("utf8")
|
|
||||||
except ApiException as exc:
|
|
||||||
if exc.reason == "Not Found":
|
|
||||||
raise CoreException("Pod {0}/{1} not found.".format(namespace, name))
|
|
||||||
raise CoreException(
|
|
||||||
"Unable to retrieve log from Pod due to: {0}".format(
|
|
||||||
get_exception_message(exc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"changed": False, "log": log, "log_lines": log.split("\n")}
|
|
||||||
|
|
||||||
|
|
||||||
def extract_selectors(instance):
|
def extract_selectors(module, instance):
|
||||||
# Parses selectors on an object based on the specifications documented here:
|
# Parses selectors on an object based on the specifications documented here:
|
||||||
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||||
selectors = []
|
selectors = []
|
||||||
if not instance.spec.selector:
|
if not instance.spec.selector:
|
||||||
raise CoreException(
|
module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
|
||||||
"{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))
|
||||||
"/".join(instance.group, instance.apiVersion), instance.kind
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not (
|
if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
|
||||||
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
|
# 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():
|
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
|
return selectors
|
||||||
|
|
||||||
if instance.spec.selector.matchLabels:
|
if instance.spec.selector.matchLabels:
|
||||||
for k, v in dict(instance.spec.selector.matchLabels).items():
|
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:
|
if instance.spec.selector.matchExpressions:
|
||||||
for expression in instance.spec.selector.matchExpressions:
|
for expression in instance.spec.selector.matchExpressions:
|
||||||
operator = expression.operator
|
operator = expression.operator
|
||||||
|
|
||||||
if operator == "Exists":
|
if operator == 'Exists':
|
||||||
selectors.append(expression.key)
|
selectors.append(expression.key)
|
||||||
elif operator == "DoesNotExist":
|
elif operator == 'DoesNotExist':
|
||||||
selectors.append("!{0}".format(expression.key))
|
selectors.append('!{0}'.format(expression.key))
|
||||||
elif operator in ["In", "NotIn"]:
|
elif operator in ['In', 'NotIn']:
|
||||||
selectors.append(
|
selectors.append('{key} {operator} {values}'.format(
|
||||||
"{key} {operator} {values}".format(
|
key=expression.key,
|
||||||
key=expression.key,
|
operator=operator.lower(),
|
||||||
operator=operator.lower(),
|
values='({0})'.format(', '.join(expression.values))
|
||||||
values="({0})".format(", ".join(expression.values)),
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise CoreException(
|
module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
|
||||||
"The k8s_log module does not support the {0} matchExpression operator".format(
|
|
||||||
operator.lower()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return selectors
|
return selectors
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_log(response):
|
||||||
|
if PY2:
|
||||||
|
return response.data
|
||||||
|
return response.data.decode('utf8')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
module_class=AnsibleModule,
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
argument_spec=argspec(),
|
K8sAnsibleMixin, get_api_client)
|
||||||
supports_check_mode=True,
|
|
||||||
mutually_exclusive=[("container", "all_containers")],
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
svc = K8sService(client, module)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
result = execute_module(svc, module.params)
|
|
||||||
module.exit_json(**result)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
module: k8s_rollback
|
module: k8s_rollback
|
||||||
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
|
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
@@ -35,18 +34,18 @@ requirements:
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Rollback a failed deployment
|
- name: Rollback a failed deployment
|
||||||
kubernetes.core.k8s_rollback:
|
kubernetes.core.k8s_rollback:
|
||||||
api_version: apps/v1
|
api_version: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
name: web
|
name: web
|
||||||
namespace: testing
|
namespace: testing
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
rollback_info:
|
rollback_info:
|
||||||
description:
|
description:
|
||||||
- The object that was rolled back.
|
- The object that was rolled back.
|
||||||
@@ -75,148 +74,108 @@ rollback_info:
|
|||||||
description: Current status details for the object.
|
description: Current status details for the object.
|
||||||
returned: success
|
returned: success
|
||||||
type: dict
|
type: dict
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||||
NAME_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_managed_resource(kind):
|
def get_managed_resource(module):
|
||||||
managed_resource = {}
|
managed_resource = {}
|
||||||
|
|
||||||
|
kind = module.params['kind']
|
||||||
if kind == "DaemonSet":
|
if kind == "DaemonSet":
|
||||||
managed_resource["kind"] = "ControllerRevision"
|
managed_resource['kind'] = "ControllerRevision"
|
||||||
managed_resource["api_version"] = "apps/v1"
|
managed_resource['api_version'] = "apps/v1"
|
||||||
elif kind == "Deployment":
|
elif kind == "Deployment":
|
||||||
managed_resource["kind"] = "ReplicaSet"
|
managed_resource['kind'] = "ReplicaSet"
|
||||||
managed_resource["api_version"] = "apps/v1"
|
managed_resource['api_version'] = "apps/v1"
|
||||||
else:
|
else:
|
||||||
raise CoreException(
|
module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
|
||||||
"Cannot perform rollback on resource of kind {0}".format(kind)
|
|
||||||
)
|
|
||||||
return managed_resource
|
return managed_resource
|
||||||
|
|
||||||
|
|
||||||
def execute_module(svc):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
results = []
|
results = []
|
||||||
module = svc.module
|
|
||||||
|
|
||||||
resources = svc.find(
|
resources = k8s_ansible_mixin.kubernetes_facts(
|
||||||
module.params["kind"],
|
module.params['kind'],
|
||||||
module.params["api_version"],
|
module.params['api_version'],
|
||||||
module.params["name"],
|
module.params['name'],
|
||||||
module.params["namespace"],
|
module.params['namespace'],
|
||||||
module.params["label_selectors"],
|
module.params['label_selectors'],
|
||||||
module.params["field_selectors"],
|
module.params['field_selectors'])
|
||||||
)
|
|
||||||
|
|
||||||
changed = False
|
for resource in resources['resources']:
|
||||||
for resource in resources["resources"]:
|
result = perform_action(module, k8s_ansible_mixin, resource)
|
||||||
result = perform_action(svc, resource)
|
|
||||||
changed = result["changed"] or changed
|
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
module.exit_json(**{"changed": changed, "rollback_info": results})
|
module.exit_json(**{
|
||||||
|
'changed': True,
|
||||||
|
'rollback_info': results
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def perform_action(svc, resource):
|
def perform_action(module, k8s_ansible_mixin, resource):
|
||||||
module = svc.module
|
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":
|
managed_resource = get_managed_resource(module)
|
||||||
current_revision = resource["metadata"]["generation"]
|
managed_resources = k8s_ansible_mixin.kubernetes_facts(
|
||||||
elif module.params["kind"] == "Deployment":
|
managed_resource['kind'],
|
||||||
current_revision = resource["metadata"]["annotations"][
|
managed_resource['api_version'],
|
||||||
"deployment.kubernetes.io/revision"
|
'',
|
||||||
]
|
module.params['namespace'],
|
||||||
|
resource['spec']
|
||||||
|
['selector']
|
||||||
|
['matchLabels'],
|
||||||
|
'')
|
||||||
|
|
||||||
managed_resource = get_managed_resource(module.params["kind"])
|
prev_managed_resource = get_previous_revision(managed_resources['resources'],
|
||||||
managed_resources = svc.find(
|
current_revision)
|
||||||
managed_resource["kind"],
|
|
||||||
managed_resource["api_version"],
|
|
||||||
"",
|
|
||||||
module.params["namespace"],
|
|
||||||
resource["spec"]["selector"]["matchLabels"],
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
prev_managed_resource = get_previous_revision(
|
if module.params['kind'] == "Deployment":
|
||||||
managed_resources["resources"], current_revision
|
del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
|
||||||
)
|
|
||||||
if not prev_managed_resource:
|
|
||||||
warn = "No rollout history found for resource %s/%s" % (
|
|
||||||
module.params["kind"],
|
|
||||||
resource["metadata"]["name"],
|
|
||||||
)
|
|
||||||
result = {"changed": False, "warnings": [warn]}
|
|
||||||
return result
|
|
||||||
|
|
||||||
if module.params["kind"] == "Deployment":
|
resource_patch = [{
|
||||||
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
"op": "replace",
|
||||||
"pod-template-hash"
|
"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 = [
|
api_target = 'deployments'
|
||||||
{
|
content_type = 'application/json-patch+json'
|
||||||
"op": "replace",
|
elif module.params['kind'] == "DaemonSet":
|
||||||
"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":
|
|
||||||
resource_patch = prev_managed_resource["data"]
|
resource_patch = prev_managed_resource["data"]
|
||||||
|
|
||||||
api_target = "daemonsets"
|
api_target = 'daemonsets'
|
||||||
content_type = "application/strategic-merge-patch+json"
|
content_type = 'application/strategic-merge-patch+json'
|
||||||
|
|
||||||
rollback = resource
|
rollback = k8s_ansible_mixin.client.request(
|
||||||
if not module.check_mode:
|
"PATCH",
|
||||||
rollback = svc.client.client.request(
|
"/apis/{0}/namespaces/{1}/{2}/{3}"
|
||||||
"PATCH",
|
.format(module.params['api_version'],
|
||||||
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
module.params['namespace'],
|
||||||
module.params["api_version"],
|
|
||||||
module.params["namespace"],
|
|
||||||
api_target,
|
api_target,
|
||||||
module.params["name"],
|
module.params['name']),
|
||||||
),
|
body=resource_patch,
|
||||||
body=resource_patch,
|
content_type=content_type)
|
||||||
content_type=content_type,
|
|
||||||
).to_dict()
|
|
||||||
|
|
||||||
result = {"changed": True}
|
result = {'changed': True}
|
||||||
result["method"] = "patch"
|
result['method'] = 'patch'
|
||||||
result["body"] = resource_patch
|
result['body'] = resource_patch
|
||||||
result["resources"] = rollback
|
result['resources'] = rollback.to_dict()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -225,8 +184,8 @@ def argspec():
|
|||||||
args.update(NAME_ARG_SPEC)
|
args.update(NAME_ARG_SPEC)
|
||||||
args.update(
|
args.update(
|
||||||
dict(
|
dict(
|
||||||
label_selectors=dict(type="list", elements="str", default=[]),
|
label_selectors=dict(type='list', elements='str', default=[]),
|
||||||
field_selectors=dict(type="list", elements="str", default=[]),
|
field_selectors=dict(type='list', elements='str', default=[]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return args
|
return args
|
||||||
@@ -234,41 +193,27 @@ def argspec():
|
|||||||
|
|
||||||
def get_previous_revision(all_resources, current_revision):
|
def get_previous_revision(all_resources, current_revision):
|
||||||
for resource in all_resources:
|
for resource in all_resources:
|
||||||
if resource["kind"] == "ReplicaSet":
|
if resource['kind'] == 'ReplicaSet':
|
||||||
if (
|
if int(resource['metadata']
|
||||||
int(
|
['annotations']
|
||||||
resource["metadata"]["annotations"][
|
['deployment.kubernetes.io/revision']) == int(current_revision) - 1:
|
||||||
"deployment.kubernetes.io/revision"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
== int(current_revision) - 1
|
|
||||||
):
|
|
||||||
return resource
|
return resource
|
||||||
elif resource["kind"] == "ControllerRevision":
|
elif resource['kind'] == 'ControllerRevision':
|
||||||
if (
|
if int(resource['metadata']
|
||||||
int(
|
['annotations']
|
||||||
resource["metadata"]["annotations"][
|
['deprecated.daemonset.template.generation']) == int(current_revision) - 1:
|
||||||
"deprecated.daemonset.template.generation"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
== int(current_revision) - 1
|
|
||||||
):
|
|
||||||
return resource
|
return resource
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (K8sAnsibleMixin, get_api_client)
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
svc = K8sService(client, module)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
execute_module(svc)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s_scale
|
module: k8s_scale
|
||||||
|
|
||||||
@@ -48,9 +48,9 @@ requirements:
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Scale deployment up, and extend timeout
|
- name: Scale deployment up, and extend timeout
|
||||||
kubernetes.core.k8s_scale:
|
kubernetes.core.k8s_scale:
|
||||||
api_version: v1
|
api_version: v1
|
||||||
@@ -105,9 +105,9 @@ EXAMPLES = r"""
|
|||||||
label_selectors:
|
label_selectors:
|
||||||
- app=test
|
- app=test
|
||||||
continue_on_error: true
|
continue_on_error: true
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- If a change was made, will return the patched object, otherwise returns the existing object.
|
- If a change was made, will return the patched object, otherwise returns the existing object.
|
||||||
@@ -139,188 +139,133 @@ result:
|
|||||||
returned: when C(wait) is true
|
returned: when C(wait) is true
|
||||||
type: int
|
type: int
|
||||||
sample: 48
|
sample: 48
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
try:
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
from kubernetes.dynamic.exceptions import NotFoundError
|
|
||||||
except ImportError:
|
|
||||||
# Handled in module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
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 (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
|
||||||
RESOURCE_ARG_SPEC,
|
|
||||||
NAME_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
ResourceTimeout,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
diff_objects,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
|
|
||||||
get_waiter,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
|
|
||||||
create_definitions,
|
|
||||||
)
|
|
||||||
|
|
||||||
SCALE_ARG_SPEC = {
|
SCALE_ARG_SPEC = {
|
||||||
"replicas": {"type": "int", "required": True},
|
'replicas': {'type': 'int', 'required': True},
|
||||||
"current_replicas": {"type": "int"},
|
'current_replicas': {'type': 'int'},
|
||||||
"resource_version": {},
|
'resource_version': {},
|
||||||
"wait": {"type": "bool", "default": True},
|
'wait': {'type': 'bool', 'default': True},
|
||||||
"wait_timeout": {"type": "int", "default": 20},
|
'wait_timeout': {'type': 'int', 'default': 20},
|
||||||
"wait_sleep": {"type": "int", "default": 5},
|
'wait_sleep': {'type': 'int', 'default': 5},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def execute_module(client, module):
|
def execute_module(module, k8s_ansible_mixin,):
|
||||||
current_replicas = module.params.get("current_replicas")
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
replicas = module.params.get("replicas")
|
|
||||||
resource_version = module.params.get("resource_version")
|
definition = k8s_ansible_mixin.resource_definitions[0]
|
||||||
definitions = create_definitions(module.params)
|
|
||||||
definition = definitions[0]
|
name = definition['metadata']['name']
|
||||||
name = definition["metadata"].get("name")
|
namespace = definition['metadata'].get('namespace')
|
||||||
namespace = definition["metadata"].get("namespace")
|
api_version = definition['apiVersion']
|
||||||
api_version = definition["apiVersion"]
|
kind = definition['kind']
|
||||||
kind = definition["kind"]
|
current_replicas = module.params.get('current_replicas')
|
||||||
label_selectors = module.params.get("label_selectors")
|
replicas = module.params.get('replicas')
|
||||||
|
resource_version = module.params.get('resource_version')
|
||||||
|
|
||||||
|
label_selectors = module.params.get('label_selectors')
|
||||||
if not label_selectors:
|
if not label_selectors:
|
||||||
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 = module.params.get('wait')
|
||||||
wait_sleep = module.params.get("wait_sleep")
|
wait_time = module.params.get('wait_timeout')
|
||||||
|
wait_sleep = module.params.get('wait_sleep')
|
||||||
existing = None
|
existing = None
|
||||||
existing_count = None
|
existing_count = None
|
||||||
return_attributes = dict(result=dict())
|
return_attributes = dict(result=dict())
|
||||||
if module._diff:
|
if module._diff:
|
||||||
return_attributes["diff"] = dict()
|
return_attributes['diff'] = dict()
|
||||||
if wait:
|
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
|
||||||
|
|
||||||
resource = client.resource(kind, api_version)
|
|
||||||
multiple_scale = False
|
multiple_scale = False
|
||||||
try:
|
try:
|
||||||
existing = resource.get(
|
existing = resource.get(name=name, namespace=namespace, label_selector=','.join(label_selectors))
|
||||||
name=name, namespace=namespace, label_selector=",".join(label_selectors)
|
if existing.kind.endswith('List'):
|
||||||
)
|
|
||||||
if existing.kind.endswith("List"):
|
|
||||||
existing_items = existing.items
|
existing_items = existing.items
|
||||||
multiple_scale = len(existing_items) > 1
|
multiple_scale = len(existing_items) > 1
|
||||||
else:
|
else:
|
||||||
existing_items = [existing]
|
existing_items = [existing]
|
||||||
except NotFoundError as e:
|
except NotFoundError as exc:
|
||||||
reason = e.body if hasattr(e, "body") else e
|
module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
||||||
msg = "Failed to retrieve requested object: {0}".format(reason)
|
error=exc.value.get('status'))
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
if multiple_scale:
|
if multiple_scale:
|
||||||
# when scaling multiple resource, the 'result' is changed to 'results' and is a list
|
# when scaling multiple resource, the 'result' is changed to 'results' and is a list
|
||||||
return_attributes = {"results": []}
|
return_attributes = {'results': []}
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
def _continue_or_fail(error):
|
def _continue_or_fail(error):
|
||||||
if multiple_scale and continue_on_error:
|
if multiple_scale and continue_on_error:
|
||||||
if "errors" not in return_attributes:
|
if "errors" not in return_attributes:
|
||||||
return_attributes["errors"] = []
|
return_attributes['errors'] = []
|
||||||
return_attributes["errors"].append({"error": error, "failed": True})
|
return_attributes['errors'].append({'error': error, 'failed': True})
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg=error, **return_attributes)
|
module.fail_json(msg=error, **return_attributes)
|
||||||
|
|
||||||
def _continue_or_exit(warn):
|
def _continue_or_exit(warn):
|
||||||
if multiple_scale:
|
if multiple_scale:
|
||||||
return_attributes["results"].append({"warning": warn, "changed": False})
|
return_attributes['results'].append({'warning': warn, 'changed': False})
|
||||||
else:
|
else:
|
||||||
module.exit_json(warning=warn, **return_attributes)
|
module.exit_json(warning=warn, **return_attributes)
|
||||||
|
|
||||||
for existing in existing_items:
|
for existing in existing_items:
|
||||||
if kind.lower() == "job":
|
if module.params['kind'] == 'job':
|
||||||
existing_count = existing.spec.parallelism
|
existing_count = existing.spec.parallelism
|
||||||
elif hasattr(existing.spec, "replicas"):
|
elif hasattr(existing.spec, 'replicas'):
|
||||||
existing_count = existing.spec.replicas
|
existing_count = existing.spec.replicas
|
||||||
|
|
||||||
if existing_count is None:
|
if existing_count is None:
|
||||||
error = "Failed to retrieve the available count for object kind={0} name={1} namespace={2}.".format(
|
error = 'Failed to retrieve the available count for object kind={0} name={1} namespace={2}.'.format(
|
||||||
existing.kind, existing.metadata.name, existing.metadata.namespace
|
existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||||
)
|
|
||||||
_continue_or_fail(error)
|
_continue_or_fail(error)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if resource_version and resource_version != existing.metadata.resourceVersion:
|
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(
|
warn = 'expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
|
||||||
resource_version,
|
resource_version, existing.metadata.resourceVersion, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||||
existing.metadata.resourceVersion,
|
|
||||||
existing.kind,
|
|
||||||
existing.metadata.name,
|
|
||||||
existing.metadata.namespace,
|
|
||||||
)
|
|
||||||
_continue_or_exit(warn)
|
_continue_or_exit(warn)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if current_replicas is not None and existing_count != current_replicas:
|
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(
|
warn = 'current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
|
||||||
current_replicas,
|
current_replicas, existing_count, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
||||||
existing_count,
|
|
||||||
existing.kind,
|
|
||||||
existing.metadata.name,
|
|
||||||
existing.metadata.namespace,
|
|
||||||
)
|
|
||||||
_continue_or_exit(warn)
|
_continue_or_exit(warn)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if existing_count != replicas:
|
if existing_count != replicas:
|
||||||
if kind.lower() == "job":
|
if not module.check_mode:
|
||||||
existing.spec.parallelism = replicas
|
if module.params['kind'] == 'job':
|
||||||
result = {"changed": True}
|
existing.spec.parallelism = replicas
|
||||||
if module.check_mode:
|
result = resource.patch(existing.to_dict()).to_dict()
|
||||||
result["result"] = existing.to_dict()
|
|
||||||
else:
|
else:
|
||||||
result["result"] = client.patch(
|
result = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time, wait_sleep)
|
||||||
resource, existing.to_dict()
|
changed = changed or result['changed']
|
||||||
).to_dict()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
result = scale(
|
|
||||||
client,
|
|
||||||
module,
|
|
||||||
resource,
|
|
||||||
existing,
|
|
||||||
replicas,
|
|
||||||
wait,
|
|
||||||
wait_time,
|
|
||||||
wait_sleep,
|
|
||||||
)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_json(msg=to_native(e))
|
|
||||||
changed = changed or result["changed"]
|
|
||||||
else:
|
else:
|
||||||
name = existing.metadata.name
|
name = existing.metadata.name
|
||||||
namespace = existing.metadata.namespace
|
namespace = existing.metadata.namespace
|
||||||
existing = client.get(resource, name=name, namespace=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:
|
if module._diff:
|
||||||
result["diff"] = {}
|
result['diff'] = {}
|
||||||
if wait:
|
if wait:
|
||||||
result["duration"] = 0
|
result['duration'] = 0
|
||||||
# append result to the return attribute
|
# append result to the return attribute
|
||||||
if multiple_scale:
|
if multiple_scale:
|
||||||
return_attributes["results"].append(result)
|
return_attributes['results'].append(result)
|
||||||
else:
|
else:
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
@@ -332,91 +277,56 @@ def argspec():
|
|||||||
args.update(RESOURCE_ARG_SPEC)
|
args.update(RESOURCE_ARG_SPEC)
|
||||||
args.update(NAME_ARG_SPEC)
|
args.update(NAME_ARG_SPEC)
|
||||||
args.update(AUTH_ARG_SPEC)
|
args.update(AUTH_ARG_SPEC)
|
||||||
args.update({"label_selectors": {"type": "list", "elements": "str", "default": []}})
|
args.update({'label_selectors': {'type': 'list', 'elements': 'str', 'default': []}})
|
||||||
args.update(({"continue_on_error": {"type": "bool", "default": False}}))
|
args.update(({'continue_on_error': {'type': 'bool', 'default': False}}))
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def scale(
|
def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time, wait_sleep):
|
||||||
client,
|
|
||||||
module,
|
|
||||||
resource,
|
|
||||||
existing_object,
|
|
||||||
replicas,
|
|
||||||
wait,
|
|
||||||
wait_time,
|
|
||||||
wait_sleep,
|
|
||||||
):
|
|
||||||
name = existing_object.metadata.name
|
name = existing_object.metadata.name
|
||||||
namespace = existing_object.metadata.namespace
|
namespace = existing_object.metadata.namespace
|
||||||
kind = existing_object.kind
|
kind = existing_object.kind
|
||||||
|
|
||||||
if not hasattr(resource, "scale"):
|
if not hasattr(resource, 'scale'):
|
||||||
raise CoreException(
|
module.fail_json(
|
||||||
"Cannot perform scale on resource of kind {0}".format(resource.kind)
|
msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
|
||||||
)
|
)
|
||||||
|
|
||||||
scale_obj = {
|
scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
|
||||||
"kind": kind,
|
|
||||||
"metadata": {"name": name, "namespace": namespace},
|
|
||||||
"spec": {"replicas": replicas},
|
|
||||||
}
|
|
||||||
|
|
||||||
existing = client.get(resource, name=name, namespace=namespace)
|
existing = resource.get(name=name, namespace=namespace)
|
||||||
|
|
||||||
|
try:
|
||||||
|
resource.scale.patch(body=scale_obj)
|
||||||
|
except Exception as exc:
|
||||||
|
module.fail_json(msg="Scale request failed: {0}".format(exc))
|
||||||
|
|
||||||
|
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 = dict()
|
||||||
if module.check_mode:
|
result['result'] = k8s_obj
|
||||||
k8s_obj = copy.deepcopy(existing.to_dict())
|
result['changed'] = not match
|
||||||
k8s_obj["spec"]["replicas"] = replicas
|
|
||||||
if wait:
|
|
||||||
result["duration"] = 0
|
|
||||||
result["result"] = k8s_obj
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
resource.scale.patch(body=scale_obj)
|
|
||||||
except Exception as e:
|
|
||||||
reason = e.body if hasattr(e, "body") else e
|
|
||||||
msg = "Scale request failed: {0}".format(reason)
|
|
||||||
raise CoreException(msg) from e
|
|
||||||
|
|
||||||
k8s_obj = client.get(resource, name=name, namespace=namespace).to_dict()
|
|
||||||
result["result"] = k8s_obj
|
|
||||||
if wait:
|
|
||||||
waiter = get_waiter(client, resource)
|
|
||||||
success, result["result"], result["duration"] = waiter.wait(
|
|
||||||
timeout=wait_time,
|
|
||||||
sleep=wait_sleep,
|
|
||||||
name=name,
|
|
||||||
namespace=namespace,
|
|
||||||
)
|
|
||||||
if not success:
|
|
||||||
raise ResourceTimeout("Resource scaling timed out", **result)
|
|
||||||
|
|
||||||
match, diffs = diff_objects(existing.to_dict(), result["result"])
|
|
||||||
result["changed"] = not match
|
|
||||||
if module._diff:
|
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)
|
||||||
|
if not success:
|
||||||
|
module.fail_json(msg="Resource scaling timed out", **result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
("resource_definition", "src"),
|
('resource_definition', 'src'),
|
||||||
]
|
]
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
|
||||||
module_class=AnsibleModule,
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
argument_spec=argspec(),
|
K8sAnsibleMixin, get_api_client)
|
||||||
mutually_exclusive=mutually_exclusive,
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
supports_check_mode=True,
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
try:
|
|
||||||
client = get_api_client(module=module)
|
|
||||||
execute_module(client, module)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
DOCUMENTATION = r'''
|
||||||
|
|
||||||
module: k8s_service
|
module: k8s_service
|
||||||
|
|
||||||
@@ -85,9 +85,9 @@ options:
|
|||||||
requirements:
|
requirements:
|
||||||
- python >= 3.6
|
- python >= 3.6
|
||||||
- kubernetes >= 12.0.0
|
- kubernetes >= 12.0.0
|
||||||
"""
|
'''
|
||||||
|
|
||||||
EXAMPLES = r"""
|
EXAMPLES = r'''
|
||||||
- name: Expose https port with ClusterIP
|
- name: Expose https port with ClusterIP
|
||||||
kubernetes.core.k8s_service:
|
kubernetes.core.k8s_service:
|
||||||
state: present
|
state: present
|
||||||
@@ -111,9 +111,9 @@ EXAMPLES = r"""
|
|||||||
protocol: TCP
|
protocol: TCP
|
||||||
selector:
|
selector:
|
||||||
key: special
|
key: special
|
||||||
"""
|
'''
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r'''
|
||||||
result:
|
result:
|
||||||
description:
|
description:
|
||||||
- The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.
|
- The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.
|
||||||
@@ -140,55 +140,32 @@ result:
|
|||||||
description: Current status details for the object.
|
description: Current status details for the object.
|
||||||
returned: success
|
returned: success
|
||||||
type: complex
|
type: complex
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||||
AnsibleModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC,
|
AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
|
||||||
COMMON_ARG_SPEC,
|
|
||||||
RESOURCE_ARG_SPEC,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
|
|
||||||
K8sService,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
|
|
||||||
create_definitions,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
|
|
||||||
perform_action,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SERVICE_ARG_SPEC = {
|
SERVICE_ARG_SPEC = {
|
||||||
"apply": {"type": "bool", "default": False},
|
'apply': {
|
||||||
"name": {"required": True},
|
'type': 'bool',
|
||||||
"namespace": {"required": True},
|
'default': False,
|
||||||
"merge_type": {
|
|
||||||
"type": "list",
|
|
||||||
"elements": "str",
|
|
||||||
"choices": ["json", "merge", "strategic-merge"],
|
|
||||||
},
|
},
|
||||||
"selector": {"type": "dict"},
|
'name': {'required': True},
|
||||||
"type": {
|
'namespace': {'required': True},
|
||||||
"type": "str",
|
'merge_type': {'type': 'list', 'elements': 'str', 'choices': ['json', 'merge', 'strategic-merge']},
|
||||||
"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'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -198,7 +175,7 @@ def merge_dicts(x, y):
|
|||||||
if isinstance(x[k], dict) and isinstance(y[k], dict):
|
if isinstance(x[k], dict) and isinstance(y[k], dict):
|
||||||
yield (k, dict(merge_dicts(x[k], y[k])))
|
yield (k, dict(merge_dicts(x[k], y[k])))
|
||||||
else:
|
else:
|
||||||
yield (k, y[k] if y[k] else x[k])
|
yield (k, y[k])
|
||||||
elif k in x:
|
elif k in x:
|
||||||
yield (k, x[k])
|
yield (k, x[k])
|
||||||
else:
|
else:
|
||||||
@@ -206,7 +183,7 @@ def merge_dicts(x, y):
|
|||||||
|
|
||||||
|
|
||||||
def argspec():
|
def argspec():
|
||||||
"""argspec property builder"""
|
""" argspec property builder """
|
||||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
argument_spec.update(COMMON_ARG_SPEC)
|
argument_spec.update(COMMON_ARG_SPEC)
|
||||||
argument_spec.update(RESOURCE_ARG_SPEC)
|
argument_spec.update(RESOURCE_ARG_SPEC)
|
||||||
@@ -214,52 +191,48 @@ def argspec():
|
|||||||
return argument_spec
|
return argument_spec
|
||||||
|
|
||||||
|
|
||||||
def execute_module(svc):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
"""Module execution"""
|
""" Module execution """
|
||||||
module = svc.module
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
api_version = "v1"
|
|
||||||
selector = module.params.get("selector")
|
api_version = 'v1'
|
||||||
service_type = module.params.get("type")
|
selector = module.params.get('selector')
|
||||||
ports = module.params.get("ports")
|
service_type = module.params.get('type')
|
||||||
|
ports = module.params.get('ports')
|
||||||
|
|
||||||
definition = defaultdict(defaultdict)
|
definition = defaultdict(defaultdict)
|
||||||
|
|
||||||
definition["kind"] = "Service"
|
definition['kind'] = 'Service'
|
||||||
definition["apiVersion"] = api_version
|
definition['apiVersion'] = api_version
|
||||||
|
|
||||||
def_spec = definition["spec"]
|
def_spec = definition['spec']
|
||||||
def_spec["type"] = service_type
|
def_spec['type'] = service_type
|
||||||
def_spec["ports"] = ports
|
def_spec['ports'] = ports
|
||||||
def_spec["selector"] = selector
|
def_spec['selector'] = selector
|
||||||
|
|
||||||
def_meta = definition["metadata"]
|
def_meta = definition['metadata']
|
||||||
def_meta["name"] = module.params.get("name")
|
def_meta['name'] = module.params.get('name')
|
||||||
def_meta["namespace"] = module.params.get("namespace")
|
def_meta['namespace'] = module.params.get('namespace')
|
||||||
|
|
||||||
definitions = create_definitions(module.params)
|
|
||||||
|
|
||||||
# 'resource_definition:' has lower priority than module parameters
|
# 'resource_definition:' has lower priority than module parameters
|
||||||
definition = dict(merge_dicts(definitions[0], definition))
|
definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
|
||||||
|
|
||||||
result = perform_action(svc, definition, module.params)
|
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)
|
||||||
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
module_class=AnsibleModule,
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
argument_spec=argspec(),
|
K8sAnsibleMixin, get_api_client)
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
svc = K8sService(client, module)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
execute_module(svc)
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,313 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2021, Alina Buzachis <@alinabuzachis>
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r"""
|
|
||||||
module: k8s_taint
|
|
||||||
short_description: Taint a node in a Kubernetes/OpenShift cluster
|
|
||||||
version_added: "2.3.0"
|
|
||||||
author: Alina Buzachis (@alinabuzachis)
|
|
||||||
description:
|
|
||||||
- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
|
|
||||||
- Untaint will remove taints from nodes as needed.
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- kubernetes.core.k8s_auth_options
|
|
||||||
options:
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Determines whether to add or remove taints.
|
|
||||||
type: str
|
|
||||||
default: present
|
|
||||||
choices: [ present, absent ]
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- The name of the node.
|
|
||||||
required: true
|
|
||||||
type: str
|
|
||||||
taints:
|
|
||||||
description:
|
|
||||||
- List containing the taints.
|
|
||||||
type: list
|
|
||||||
required: true
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
key:
|
|
||||||
description:
|
|
||||||
- The taint key to be applied to a node.
|
|
||||||
type: str
|
|
||||||
value:
|
|
||||||
description:
|
|
||||||
- The taint value corresponding to the taint key.
|
|
||||||
type: str
|
|
||||||
effect:
|
|
||||||
description:
|
|
||||||
- The effect of the taint on Pods that do not tolerate the taint.
|
|
||||||
- Required when I(state=present).
|
|
||||||
type: str
|
|
||||||
choices: [ NoSchedule, NoExecute, PreferNoSchedule ]
|
|
||||||
replace:
|
|
||||||
description:
|
|
||||||
- If C(true), allow taints to be replaced.
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: bool
|
|
||||||
requirements:
|
|
||||||
- python >= 3.6
|
|
||||||
- kubernetes >= 12.0.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = r"""
|
|
||||||
- name: Taint node "foo"
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: present
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
|
|
||||||
- name: Taint node "foo"
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: present
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
- effect: NoSchedule
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
|
|
||||||
- name: Remove taint from "foo".
|
|
||||||
kubernetes.core.k8s_taint:
|
|
||||||
state: absent
|
|
||||||
name: foo
|
|
||||||
taints:
|
|
||||||
- effect: NoExecute
|
|
||||||
key: "key1"
|
|
||||||
value: "value1"
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = r"""
|
|
||||||
result:
|
|
||||||
description:
|
|
||||||
- The tainted Node object. Will be empty in the case of a deletion.
|
|
||||||
returned: success
|
|
||||||
type: complex
|
|
||||||
contains:
|
|
||||||
api_version:
|
|
||||||
description: The versioned schema of this representation of an object.
|
|
||||||
returned: success
|
|
||||||
type: str
|
|
||||||
kind:
|
|
||||||
description: Represents the REST resource this object represents.
|
|
||||||
returned: success
|
|
||||||
type: str
|
|
||||||
metadata:
|
|
||||||
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
|
|
||||||
returned: success
|
|
||||||
type: complex
|
|
||||||
spec:
|
|
||||||
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
|
|
||||||
returned: success
|
|
||||||
type: complex
|
|
||||||
status:
|
|
||||||
description: Current status details for the object.
|
|
||||||
returned: success
|
|
||||||
type: complex
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
||||||
get_api_client,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
||||||
AnsibleK8SModule,
|
|
||||||
)
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
||||||
CoreException,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from kubernetes.client.api import core_v1_api
|
|
||||||
from kubernetes.client.exceptions import ApiException
|
|
||||||
except ImportError:
|
|
||||||
# ImportErrors are handled during module setup
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _equal_dicts(a, b):
|
|
||||||
keys = ["key", "effect"]
|
|
||||||
if "effect" not in set(a).intersection(b):
|
|
||||||
keys.remove("effect")
|
|
||||||
|
|
||||||
return all((a[x] == b[x] for x in keys))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_difference(a, b):
|
|
||||||
return [
|
|
||||||
a_item for a_item in a if not any(_equal_dicts(a_item, b_item) for b_item in b)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _get_intersection(a, b):
|
|
||||||
return [a_item for a_item in a if any(_equal_dicts(a_item, b_item) for b_item in b)]
|
|
||||||
|
|
||||||
|
|
||||||
def _update_exists(a, b):
|
|
||||||
return any(
|
|
||||||
(
|
|
||||||
any(
|
|
||||||
_equal_dicts(a_item, b_item)
|
|
||||||
and a_item.get("value") != b_item.get("value")
|
|
||||||
for b_item in b
|
|
||||||
)
|
|
||||||
for a_item in a
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
|
||||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
|
||||||
argument_spec.update(
|
|
||||||
dict(
|
|
||||||
state=dict(type="str", choices=["present", "absent"], default="present"),
|
|
||||||
name=dict(type="str", required=True),
|
|
||||||
taints=dict(type="list", required=True, elements="dict"),
|
|
||||||
replace=dict(type="bool", default=False),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return argument_spec
|
|
||||||
|
|
||||||
|
|
||||||
class K8sTaintAnsible:
|
|
||||||
def __init__(self, module, client):
|
|
||||||
self.module = module
|
|
||||||
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
|
||||||
self.changed = False
|
|
||||||
|
|
||||||
def get_node(self, 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}' has not been found.".format(name))
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return node
|
|
||||||
|
|
||||||
def patch_node(self, taints):
|
|
||||||
body = {"spec": {"taints": taints}}
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = 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))
|
|
||||||
)
|
|
||||||
|
|
||||||
return result.to_dict()
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
result = {"result": {}}
|
|
||||||
state = self.module.params.get("state")
|
|
||||||
taints = self.module.params.get("taints")
|
|
||||||
name = self.module.params.get("name")
|
|
||||||
|
|
||||||
node = self.get_node(name)
|
|
||||||
existing_taints = node.spec.to_dict().get("taints") or []
|
|
||||||
diff = _get_difference(taints, existing_taints)
|
|
||||||
|
|
||||||
if state == "present":
|
|
||||||
if diff:
|
|
||||||
# There are new taints to be added
|
|
||||||
self.changed = True
|
|
||||||
if self.module.check_mode:
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
|
|
||||||
if self.module.params.get("replace"):
|
|
||||||
# Patch with the new taints
|
|
||||||
result["result"] = self.patch_node(taints=taints)
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
|
|
||||||
result["result"] = self.patch_node(
|
|
||||||
taints=[*_get_difference(existing_taints, taints), *taints]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# No new taints to be added, but maybe there is something to be updated
|
|
||||||
if _update_exists(existing_taints, taints):
|
|
||||||
self.changed = True
|
|
||||||
if self.module.check_mode:
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
result["result"] = self.patch_node(
|
|
||||||
taints=[*_get_difference(existing_taints, taints), *taints]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result["result"] = node.to_dict()
|
|
||||||
elif state == "absent":
|
|
||||||
# Nothing to be removed
|
|
||||||
if not existing_taints:
|
|
||||||
result["result"] = node.to_dict()
|
|
||||||
if not diff:
|
|
||||||
self.changed = True
|
|
||||||
if self.module.check_mode:
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
self.patch_node(taints=_get_difference(existing_taints, taints))
|
|
||||||
else:
|
|
||||||
if _get_intersection(existing_taints, taints):
|
|
||||||
self.changed = True
|
|
||||||
if self.module.check_mode:
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
self.patch_node(taints=_get_difference(existing_taints, taints))
|
|
||||||
else:
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
|
|
||||||
self.module.exit_json(changed=self.changed, **result)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
module = AnsibleK8SModule(
|
|
||||||
module_class=AnsibleModule,
|
|
||||||
argument_spec=argspec(),
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
client = get_api_client(module)
|
|
||||||
k8s_taint = K8sTaintAnsible(module, client.client)
|
|
||||||
k8s_taint.execute_module()
|
|
||||||
except CoreException as e:
|
|
||||||
module.fail_from_exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
ignore = W503,E402
|
ignore = W503,E402
|
||||||
exclude = .cache,.git,.tox,tests/output
|
exclude = .cache
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
kubernetes-validate
|
kubernetes-validate
|
||||||
coverage==4.5.4
|
coverage==4.5.4
|
||||||
mock
|
|
||||||
pytest
|
pytest
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
pytest-mock
|
|
||||||
pytest-forked
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
modules:
|
|
||||||
python_requires: ">=3.6"
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
time=100
|
# slow - 11min
|
||||||
|
slow
|
||||||
|
time=313
|
||||||
helm_info
|
helm_info
|
||||||
|
helm_plugin
|
||||||
|
helm_plugin_info
|
||||||
helm_repository
|
helm_repository
|
||||||
helm_template
|
helm_template
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
helm_default_archive_name: "helm-{{ helm_version }}-{{ ansible_system | lower }}-amd64.tar.gz"
|
helm_archive_name: "helm-{{ helm_version }}-{{ ansible_system | lower }}-amd64.tar.gz"
|
||||||
helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
|
helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
|
||||||
|
|
||||||
chart_test: "ingress-nginx"
|
chart_test: "ingress-nginx"
|
||||||
chart_test_local_path: "nginx-ingress"
|
chart_test_local_path: "nginx-ingress"
|
||||||
chart_test_version: 4.2.4
|
chart_test_version: 3.8.0
|
||||||
chart_test_version_local_path: 1.32.0
|
chart_test_version_local_path: 1.32.0
|
||||||
chart_test_version_upgrade: 4.2.5
|
chart_test_version_upgrade: 3.9.0
|
||||||
chart_test_version_upgrade_local_path: 1.33.0
|
chart_test_version_upgrade_local_path: 1.33.0
|
||||||
chart_test_repo: "https://kubernetes.github.io/ingress-nginx"
|
chart_test_repo: "https://kubernetes.github.io/ingress-nginx"
|
||||||
chart_test_git_repo: "http://github.com/helm/charts.git"
|
chart_test_git_repo: "http://github.com/helm/charts.git"
|
||||||
@@ -15,12 +15,13 @@ chart_test_values:
|
|||||||
myValue: "changed"
|
myValue: "changed"
|
||||||
|
|
||||||
test_namespace:
|
test_namespace:
|
||||||
- "helm-test-crds"
|
- "helm-diff"
|
||||||
|
- "helm-envvars"
|
||||||
- "helm-uninstall"
|
- "helm-uninstall"
|
||||||
- "helm-read-envvars"
|
- "helm-not-installed"
|
||||||
- "helm-dep-update"
|
- "helm-crd"
|
||||||
|
- "helm-url"
|
||||||
|
- "helm-repository"
|
||||||
- "helm-local-path-001"
|
- "helm-local-path-001"
|
||||||
- "helm-local-path-002"
|
- "helm-local-path-002"
|
||||||
- "helm-local-path-003"
|
- "helm-local-path-003"
|
||||||
- "helm-from-repository"
|
|
||||||
- "helm-from-url"
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
apiVersion: v2
|
|
||||||
name: dep_up
|
|
||||||
description: A Helm chart for molecule test
|
|
||||||
type: application
|
|
||||||
version: 0.1.0
|
|
||||||
appVersion: "default"
|
|
||||||
dependencies:
|
|
||||||
- name: test-chart
|
|
||||||
repository: file://../test-chart
|
|
||||||
version: "0.1.0"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
chart-test:
|
|
||||||
myValue: helm update dependency test
|
|
||||||
@@ -62,8 +62,7 @@ from ansible.module_utils.basic import AnsibleModule
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type="path"),
|
binary_path=dict(type="path"), version=dict(type="str", default="3.7.0"),
|
||||||
version=dict(type="str", default="3.7.0"),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
|
|
||||||
- name: Unarchive Helm binary
|
- name: Unarchive Helm binary
|
||||||
unarchive:
|
unarchive:
|
||||||
src: 'https://get.helm.sh/{{ helm_archive_name | default(helm_default_archive_name) }}'
|
src: 'https://get.helm.sh/{{ helm_archive_name }}'
|
||||||
dest: /tmp/helm/
|
dest: /tmp/helm/
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
retries: 10
|
|
||||||
delay: 5
|
|
||||||
register: result
|
|
||||||
until: result is not failed
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
loop_control:
|
loop_control:
|
||||||
loop_var: helm_version
|
loop_var: helm_version
|
||||||
with_items:
|
with_items:
|
||||||
- "v3.7.0"
|
- "v3.2.4"
|
||||||
|
|||||||
@@ -10,12 +10,14 @@
|
|||||||
include_tasks: test_helm_not_installed.yml
|
include_tasks: test_helm_not_installed.yml
|
||||||
|
|
||||||
- name: "Install {{ helm_version }}"
|
- name: "Install {{ helm_version }}"
|
||||||
include_role:
|
include_tasks: install.yml
|
||||||
name: install_helm
|
|
||||||
|
|
||||||
- name: "Ensure we honor the environment variables"
|
- name: "Ensure we honor the environment variables"
|
||||||
include_tasks: test_read_envvars.yml
|
include_tasks: test_read_envvars.yml
|
||||||
|
|
||||||
|
- name: tests_repository
|
||||||
|
include_tasks: tests_repository.yml
|
||||||
|
|
||||||
- name: Deploy charts
|
- name: Deploy charts
|
||||||
include_tasks: "tests_chart/{{ test_chart_type }}.yml"
|
include_tasks: "tests_chart/{{ test_chart_type }}.yml"
|
||||||
loop_control:
|
loop_control:
|
||||||
@@ -25,11 +27,11 @@
|
|||||||
- from_repository
|
- from_repository
|
||||||
- from_url
|
- from_url
|
||||||
|
|
||||||
- name: test helm dependency update
|
- name: Test helm plugin
|
||||||
include_tasks: test_up_dep.yml
|
include_tasks: tests_helm_plugin.yml
|
||||||
|
|
||||||
- name: Test helm uninstall
|
- name: Test helm diff
|
||||||
include_tasks: test_helm_uninstall.yml
|
include_tasks: tests_helm_diff.yml
|
||||||
|
|
||||||
# https://github.com/ansible-collections/community.kubernetes/issues/296
|
# https://github.com/ansible-collections/community.kubernetes/issues/296
|
||||||
- name: Test Skip CRDS feature in helm chart install
|
- name: Test Skip CRDS feature in helm chart install
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
- name: Test CRDs
|
- name: Test CRDs
|
||||||
vars:
|
vars:
|
||||||
test_chart: "test-crds"
|
test_chart: "test-crds"
|
||||||
helm_namespace: "{{ test_namespace[0] }}"
|
|
||||||
block:
|
block:
|
||||||
- name: Create namespace
|
- name: Create namespace
|
||||||
k8s:
|
k8s:
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
name: "{{ helm_namespace }}"
|
name: "{{ test_namespace[4] }}"
|
||||||
|
|
||||||
- name: Copy test chart
|
- name: Copy test chart
|
||||||
copy:
|
copy:
|
||||||
@@ -18,7 +17,7 @@
|
|||||||
helm:
|
helm:
|
||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[4] }}"
|
||||||
name: test-crds
|
name: test-crds
|
||||||
skip_crds: true
|
skip_crds: true
|
||||||
register: install
|
register: install
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
apiVersion: ansible.com/v1
|
apiVersion: ansible.com/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
metadata:
|
metadata:
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[4] }}"
|
||||||
name: test-foo
|
name: test-foo
|
||||||
foobar: footest
|
foobar: footest
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
@@ -49,7 +48,7 @@
|
|||||||
- name: Uninstall chart
|
- name: Uninstall chart
|
||||||
helm:
|
helm:
|
||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[4] }}"
|
||||||
name: test-crds
|
name: test-crds
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
helm:
|
helm:
|
||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[4] }}"
|
||||||
name: test-crds
|
name: test-crds
|
||||||
|
|
||||||
- name: Create custom resource
|
- name: Create custom resource
|
||||||
@@ -66,7 +65,7 @@
|
|||||||
apiVersion: ansible.com/v1
|
apiVersion: ansible.com/v1
|
||||||
kind: Foo
|
kind: Foo
|
||||||
metadata:
|
metadata:
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[4] }}"
|
||||||
name: test-foo
|
name: test-foo
|
||||||
foobar: footest
|
foobar: footest
|
||||||
register: result
|
register: result
|
||||||
@@ -86,7 +85,7 @@
|
|||||||
- name: Remove namespace
|
- name: Remove namespace
|
||||||
k8s:
|
k8s:
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
name: "{{ helm_namespace }}"
|
name: "{{ test_namespace[4] }}"
|
||||||
state: absent
|
state: absent
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
binary_path: "{{ helm_binary}}_fake"
|
binary_path: "{{ helm_binary}}_fake"
|
||||||
name: test
|
name: test
|
||||||
chart_ref: "{{ chart_test }}"
|
chart_ref: "{{ chart_test }}"
|
||||||
namespace: "helm-test"
|
namespace: "{{ test_namespace[3] }}"
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: helm_missing_binary
|
register: helm_missing_binary
|
||||||
|
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
- name: validate helm version lower than 3.7.0
|
|
||||||
helm_test_version:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
version: "3.7.0"
|
|
||||||
register: test_version
|
|
||||||
|
|
||||||
- vars:
|
|
||||||
chart_source: "https://github.com/kubernetes/kube-state-metrics/releases/download/kube-state-metrics-helm-chart-2.13.3/kube-state-metrics-2.13.3.tgz"
|
|
||||||
chart_name: "test-wait-uninstall"
|
|
||||||
helm_namespace: "{{ test_namespace[1] }}"
|
|
||||||
block:
|
|
||||||
|
|
||||||
- name: Install chart
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: "{{ chart_name }}"
|
|
||||||
chart_ref: "{{ chart_source }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
create_namespace: true
|
|
||||||
|
|
||||||
- name: Delete chart with wait
|
|
||||||
helm:
|
|
||||||
state: absent
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: "{{ chart_name }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
wait: yes
|
|
||||||
register: uninstall
|
|
||||||
|
|
||||||
- name: assert warning has been raised
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- uninstall.warnings
|
|
||||||
|
|
||||||
- name: Create temp directory
|
|
||||||
tempfile:
|
|
||||||
state: directory
|
|
||||||
suffix: .test
|
|
||||||
register: _result
|
|
||||||
|
|
||||||
- set_fact:
|
|
||||||
helm_tmp_dir: "{{ _result.path }}"
|
|
||||||
|
|
||||||
- name: Unarchive Helm binary
|
|
||||||
unarchive:
|
|
||||||
src: 'https://get.helm.sh/helm-v3.7.0-linux-amd64.tar.gz'
|
|
||||||
dest: "{{ helm_tmp_dir }}"
|
|
||||||
remote_src: yes
|
|
||||||
|
|
||||||
- name: Install chart
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
|
|
||||||
name: "{{ chart_name }}"
|
|
||||||
chart_ref: "{{ chart_source }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
create_namespace: true
|
|
||||||
|
|
||||||
- name: uninstall chart again using recent version
|
|
||||||
helm:
|
|
||||||
state: absent
|
|
||||||
binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
|
|
||||||
name: "{{ chart_name }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
wait: yes
|
|
||||||
register: uninstall
|
|
||||||
|
|
||||||
always:
|
|
||||||
- name: Delete temp directory
|
|
||||||
file:
|
|
||||||
path: "{{ helm_tmp_dir }}"
|
|
||||||
state: absent
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Remove namespace
|
|
||||||
k8s:
|
|
||||||
kind: Namespace
|
|
||||||
name: "{{ helm_namespace }}"
|
|
||||||
state: absent
|
|
||||||
ignore_errors: true
|
|
||||||
when: test_version.result
|
|
||||||
@@ -3,10 +3,8 @@
|
|||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
state: absent
|
state: absent
|
||||||
name: does-not-exist
|
name: does-not-exist
|
||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ test_namespace[1] }}"
|
||||||
environment:
|
environment:
|
||||||
K8S_AUTH_HOST: somewhere
|
K8S_AUTH_HOST: somewhere
|
||||||
vars:
|
|
||||||
helm_namespace: "{{ test_namespace[2] }}"
|
|
||||||
register: _helm_result
|
register: _helm_result
|
||||||
failed_when: '"http://somewhere/version" not in _helm_result.stderr'
|
failed_when: '"http://somewhere/version" not in _helm_result.stderr'
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
# Helm module
|
|
||||||
- name: "Test dependency update for helm module"
|
|
||||||
vars:
|
|
||||||
helm_namespace: "{{ test_namespace[3] }}"
|
|
||||||
block:
|
|
||||||
- name: copy chart
|
|
||||||
copy:
|
|
||||||
src: "{{ item }}"
|
|
||||||
dest: /tmp
|
|
||||||
loop:
|
|
||||||
- test-chart
|
|
||||||
- dep-up
|
|
||||||
|
|
||||||
- name: "Test chart with dependency_update false"
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: test
|
|
||||||
chart_ref: "/tmp/test-chart"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
dependency_update: false
|
|
||||||
create_namespace: yes
|
|
||||||
register: release
|
|
||||||
|
|
||||||
- name: "Get stats of the subchart"
|
|
||||||
stat:
|
|
||||||
path: "/tmp/test-chart/Chart.lock"
|
|
||||||
register: stat_result
|
|
||||||
|
|
||||||
- name: "Check if the subchart not exist in chart"
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- not stat_result.stat.exists
|
|
||||||
success_msg: "subchart not exist in the chart directory"
|
|
||||||
fail_msg: "subchart exist in the charts directory"
|
|
||||||
|
|
||||||
- name: "Test chart without dependencies block and dependency_update true"
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: test
|
|
||||||
chart_ref: "/tmp/test-chart"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
create_namespace: yes
|
|
||||||
dependency_update: true
|
|
||||||
ignore_errors: true
|
|
||||||
register: release
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- release.warnings[0] == "There is no dependencies block defined in Chart.yaml. Dependency update will not be performed. Please consider add dependencies block or disable dependency_update to remove this warning."
|
|
||||||
success_msg: "warning when there is no dependencies block with dependency_update enabled"
|
|
||||||
|
|
||||||
- name: "Test chart with dependencies block and dependency_update true"
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: test
|
|
||||||
chart_ref: "/tmp/dep-up"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
dependency_update: true
|
|
||||||
create_namespace: yes
|
|
||||||
register: release
|
|
||||||
|
|
||||||
- name: "Get stats of the subchart"
|
|
||||||
stat:
|
|
||||||
path: "/tmp/dep-up/Chart.lock"
|
|
||||||
register: stat_result
|
|
||||||
|
|
||||||
- name: "Check if the subchart exists in chart"
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- stat_result.stat.exists
|
|
||||||
success_msg: "subchart exist in the chart directory"
|
|
||||||
fail_msg: "subchart not exist in the charts directory"
|
|
||||||
always:
|
|
||||||
- name: Remove helm namespace
|
|
||||||
k8s:
|
|
||||||
api_version: v1
|
|
||||||
kind: Namespace
|
|
||||||
name: "{{ helm_namespace }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: "Remove charts"
|
|
||||||
file:
|
|
||||||
state: absent
|
|
||||||
path: "/tmp/{{ item }}"
|
|
||||||
loop:
|
|
||||||
- test-chart
|
|
||||||
- dep-up
|
|
||||||
|
|
||||||
# Helm_template module
|
|
||||||
- name: "Test dependency update for helm_template module"
|
|
||||||
block:
|
|
||||||
- name: copy chart
|
|
||||||
copy:
|
|
||||||
src: "{{ item }}"
|
|
||||||
dest: /tmp
|
|
||||||
loop:
|
|
||||||
- test-chart
|
|
||||||
- dep-up
|
|
||||||
|
|
||||||
- name: Test Helm dependency update true
|
|
||||||
helm_template:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
chart_ref: "/tmp/dep-up"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
dependency_update: true
|
|
||||||
output_dir: "/tmp"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: "Get stats of the subchart"
|
|
||||||
stat:
|
|
||||||
path: "{{ item }}"
|
|
||||||
register: stat_result
|
|
||||||
loop:
|
|
||||||
- /tmp/dep-up/Chart.lock
|
|
||||||
- /tmp/dep_up/charts/test-chart/templates/configmap.yaml
|
|
||||||
|
|
||||||
- name: "Check if the subchart exist in chart"
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- stat_result.results[0].stat.exists
|
|
||||||
- stat_result.results[1].stat.exists
|
|
||||||
success_msg: "subchart exist in the charts directory"
|
|
||||||
fail_msg: "There is no Subchart pulled"
|
|
||||||
|
|
||||||
- name: Test Helm subchart not pulled when dependency_update false for helm_template
|
|
||||||
helm_template:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
chart_ref: "/tmp/test-chart"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
dependency_update: false
|
|
||||||
output_dir: "/tmp"
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: "Get stats of the subchart"
|
|
||||||
stat:
|
|
||||||
path: "{{ item }}"
|
|
||||||
register: stat_result
|
|
||||||
loop:
|
|
||||||
- /tmp/test-chart/Chart.lock
|
|
||||||
- /tmp/test-chart/templates/configmap.yaml
|
|
||||||
|
|
||||||
- name: "Check if the subchart not exist in chart"
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- not stat_result.results[0].stat.exists
|
|
||||||
- stat_result.results[1].stat.exists
|
|
||||||
success_msg: "subchart not exist in the charts directory"
|
|
||||||
fail_msg: "There is no Subchart pulled"
|
|
||||||
always:
|
|
||||||
|
|
||||||
- name: "Remove charts"
|
|
||||||
file:
|
|
||||||
state: absent
|
|
||||||
path: "/tmp/{{ item }}"
|
|
||||||
loop:
|
|
||||||
- test-chart
|
|
||||||
- dep-up
|
|
||||||
@@ -83,21 +83,11 @@
|
|||||||
namespace: "{{ helm_namespace }}"
|
namespace: "{{ helm_namespace }}"
|
||||||
register: content_info
|
register: content_info
|
||||||
|
|
||||||
- name: Check helm_info content using release_state
|
|
||||||
helm_info:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: "{{ chart_release_name }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
release_state:
|
|
||||||
- deployed
|
|
||||||
register: release_state_content_info
|
|
||||||
|
|
||||||
- name: "Assert that {{ chart_test }} is installed from {{ source }} with helm_info"
|
- name: "Assert that {{ chart_test }} is installed from {{ source }} with helm_info"
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- content_info.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
|
- content_info.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
|
||||||
- content_info.status.status | lower == 'deployed'
|
- content_info.status.status | lower == 'deployed'
|
||||||
- release_state_content_info.status.status | lower == 'deployed'
|
|
||||||
|
|
||||||
- name: Check idempotency
|
- name: Check idempotency
|
||||||
helm:
|
helm:
|
||||||
@@ -335,13 +325,6 @@
|
|||||||
that:
|
that:
|
||||||
- not (install is changed)
|
- not (install is changed)
|
||||||
|
|
||||||
- name: "Remove {{ chart_release_name }} release"
|
|
||||||
helm:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
name: "{{ chart_release_name }}"
|
|
||||||
namespace: "{{ helm_namespace }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Render templates
|
- name: Render templates
|
||||||
helm_template:
|
helm_template:
|
||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
@@ -368,31 +351,6 @@
|
|||||||
that:
|
that:
|
||||||
result.stat.exists
|
result.stat.exists
|
||||||
|
|
||||||
- name: Render single template from chart to result
|
|
||||||
helm_template:
|
|
||||||
binary_path: "{{ helm_binary }}"
|
|
||||||
chart_ref: "{{ chart_source }}"
|
|
||||||
chart_version: "{{ chart_source_version | default(omit) }}"
|
|
||||||
disable_hook: True
|
|
||||||
release_name: "MyRelease"
|
|
||||||
release_namespace: "MyReleaseNamespace"
|
|
||||||
show_only:
|
|
||||||
- "templates/configmap.yaml"
|
|
||||||
release_values:
|
|
||||||
"myValue": "ThisValue"
|
|
||||||
register: result
|
|
||||||
when: chart_source is search("test-chart")
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result is changed
|
|
||||||
- result is not failed
|
|
||||||
- result.rc == 0
|
|
||||||
- result.command is match("{{ helm_binary }} template MyRelease {{ chart_source }}")
|
|
||||||
- result.stdout is search("ThisValue")
|
|
||||||
when: chart_source is search("test-chart")
|
|
||||||
# limit assertion of test result to controlled (local) chart_source
|
|
||||||
|
|
||||||
- name: Release using non-existent context
|
- name: Release using non-existent context
|
||||||
helm:
|
helm:
|
||||||
binary_path: "{{ helm_binary }}"
|
binary_path: "{{ helm_binary }}"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user