mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-11 20:12:18 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69304d1c3b | ||
|
|
fdddb6f78f | ||
|
|
e8cf1ef517 | ||
|
|
6dbc0d5b6d | ||
|
|
0a72c87d2c | ||
|
|
c149394556 | ||
|
|
e2dec91460 | ||
|
|
edf104d687 | ||
|
|
7b09c01d98 | ||
|
|
7409eaf993 | ||
|
|
321b6dcdd8 | ||
|
|
68d45af767 | ||
|
|
346808ec4b | ||
|
|
767aa10b1d | ||
|
|
d68dec3b90 | ||
|
|
30e84faa24 | ||
|
|
fd61f8b15d | ||
|
|
db78d3a505 | ||
|
|
73499d9a09 | ||
|
|
7031829897 | ||
|
|
44c8cff78b | ||
|
|
aae5960dce | ||
|
|
691f0cb235 | ||
|
|
951be74dc0 | ||
|
|
1f79a03edf | ||
|
|
bf3fe91a5d | ||
|
|
791175daef | ||
|
|
e62a271faf | ||
|
|
583de3217c | ||
|
|
82565dad78 | ||
|
|
dde6eb3c06 | ||
|
|
a122bad685 | ||
|
|
b54e9ef4ef | ||
|
|
acb015c788 | ||
|
|
ed33d0b56e | ||
|
|
10cffc5032 | ||
|
|
9a0b3fe30c | ||
|
|
c3ecb64b72 | ||
|
|
50a1bd9db0 | ||
|
|
04e14c1f95 | ||
|
|
b19ff9d70a | ||
|
|
526f0454ab | ||
|
|
e77c8f1449 | ||
|
|
79699ba429 | ||
|
|
fa65698362 | ||
|
|
4ae1856b5c | ||
|
|
ef46c352d0 | ||
|
|
a62c42782f | ||
|
|
ba5cb30305 | ||
|
|
39b6c43ab7 | ||
|
|
b0f1501cd4 | ||
|
|
1116056eeb | ||
|
|
60933457e8 | ||
|
|
9e2d78404f | ||
|
|
bf26f5a3be | ||
|
|
91b80b1d1d | ||
|
|
4010987d1f | ||
|
|
281ff563ed | ||
|
|
ff43353de6 | ||
|
|
d6c06a2078 | ||
|
|
8436ad1341 | ||
|
|
c65512357d | ||
|
|
8e46f92703 | ||
|
|
ab0e38753b | ||
|
|
6061586289 | ||
|
|
45ba8b1a0d | ||
|
|
d01e4a6e4d | ||
|
|
938f7e12e8 | ||
|
|
24ac45741d |
4
.github/patchback.yml
vendored
Normal file
4
.github/patchback.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
backport_branch_prefix: patchback/backports/
|
||||||
|
backport_label_prefix: backport-
|
||||||
|
target_branch_prefix: stable-
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -15,4 +15,8 @@ tests/integration/cloud-config-*
|
|||||||
.cache
|
.cache
|
||||||
|
|
||||||
# Helm charts
|
# Helm charts
|
||||||
molecule/default/*-chart-*.tgz
|
tests/integration/*-chart-*.tgz
|
||||||
|
|
||||||
|
# ansible-test generated file
|
||||||
|
tests/integration/inventory
|
||||||
|
tests/integration/*-*.yml
|
||||||
|
|||||||
@@ -5,6 +5,75 @@ Kubernetes Collection Release Notes
|
|||||||
.. contents:: Topics
|
.. contents:: Topics
|
||||||
|
|
||||||
|
|
||||||
|
v2.3.2
|
||||||
|
======
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- helm_repository - mark `pass_credentials` as no_log=True to silence false warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
|
||||||
|
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
|
||||||
|
|
||||||
|
v2.3.1
|
||||||
|
======
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Catch exception 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).
|
||||||
|
|
||||||
|
v2.3.0
|
||||||
|
======
|
||||||
|
|
||||||
|
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
|
||||||
|
--------
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- 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
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -1,5 +1,5 @@
|
|||||||
# Also needs to be updated in galaxy.yml
|
# Also needs to be updated in galaxy.yml
|
||||||
VERSION = 2.2.0
|
VERSION = 2.3.2
|
||||||
|
|
||||||
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]))'`
|
||||||
@@ -22,10 +22,7 @@ test-sanity:
|
|||||||
ansible-test sanity --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
ansible-test sanity --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
||||||
|
|
||||||
test-integration:
|
test-integration:
|
||||||
ansible-test integration --docker -v --color --retry-on-error --python $(PYTHON_VERSION) --continue-on-error --diff --coverage $(?TEST_ARGS)
|
ansible-test integration --diff --no-temp-workdir --color --skip-tags False --retry-on-error --continue-on-error --python $(PYTHON_VERSION) -v --coverage $(?TEST_ARGS)
|
||||||
|
|
||||||
test-molecule:
|
|
||||||
molecule test
|
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
ansible-test units --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
ansible-test units --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
||||||
|
|||||||
48
PSF-license.txt
Normal file
48
PSF-license.txt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||||
|
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||||
|
otherwise using this software ("Python") in source or binary form and
|
||||||
|
its associated documentation.
|
||||||
|
|
||||||
|
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||||
|
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||||
|
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||||
|
distribute, and otherwise use Python alone or in any derivative version,
|
||||||
|
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||||
|
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||||
|
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
|
||||||
|
All Rights Reserved" are retained in Python alone or in any derivative version
|
||||||
|
prepared by Licensee.
|
||||||
|
|
||||||
|
3. In the event Licensee prepares a derivative work that is based on
|
||||||
|
or incorporates Python or any part thereof, and wants to make
|
||||||
|
the derivative work available to others as provided herein, then
|
||||||
|
Licensee hereby agrees to include in any such work a brief summary of
|
||||||
|
the changes made to Python.
|
||||||
|
|
||||||
|
4. PSF is making Python available to Licensee on an "AS IS"
|
||||||
|
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||||
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||||
|
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||||
|
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||||
|
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||||
|
|
||||||
|
6. This License Agreement will automatically terminate upon a material
|
||||||
|
breach of its terms and conditions.
|
||||||
|
|
||||||
|
7. Nothing in this License Agreement shall be deemed to create any
|
||||||
|
relationship of agency, partnership, or joint venture between PSF and
|
||||||
|
Licensee. This License Agreement does not grant permission to use PSF
|
||||||
|
trademarks or trade name in a trademark sense to endorse or promote
|
||||||
|
products or services of Licensee, or any third party.
|
||||||
|
|
||||||
|
8. By copying, installing or otherwise using Python, Licensee
|
||||||
|
agrees to be bound by the terms and conditions of this License
|
||||||
|
Agreement.
|
||||||
14
README.md
14
README.md
@@ -11,6 +11,8 @@ 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.
|
||||||
@@ -22,6 +24,10 @@ 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:
|
||||||
@@ -68,6 +74,7 @@ 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-->
|
||||||
|
|
||||||
@@ -85,7 +92,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.2.0
|
version: 2.3.2
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installing the Kubernetes Python Library
|
### Installing the Kubernetes Python Library
|
||||||
@@ -165,7 +172,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, 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 for modules, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
@@ -176,6 +183,9 @@ 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 +1,2 @@
|
|||||||
kubernetes-client [platform:fedora]
|
kubernetes-client [platform:fedora]
|
||||||
|
openshift-clients [platform:rhel-8]
|
||||||
|
|||||||
@@ -486,3 +486,115 @@ releases:
|
|||||||
name: kustomize
|
name: kustomize
|
||||||
namespace: null
|
namespace: null
|
||||||
release_date: '2021-09-15'
|
release_date: '2021-09-15'
|
||||||
|
2.3.0:
|
||||||
|
changes:
|
||||||
|
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.
|
||||||
|
- 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).
|
||||||
|
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).
|
||||||
|
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
|
||||||
|
- 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
|
||||||
|
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
|
||||||
|
- 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
|
||||||
|
- _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.3.2:
|
||||||
|
changes:
|
||||||
|
minor_changes:
|
||||||
|
- helm_repository - mark `pass_credentials` as no_log=True to silence false
|
||||||
|
warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
|
||||||
|
- 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:
|
||||||
|
- 412_pass_creds.yml
|
||||||
|
- 456-replace-distutils.yml
|
||||||
|
release_date: '2022-06-09'
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ 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
|
||||||
k8s:
|
kubernetes.core.k8s:
|
||||||
state: present
|
state: present
|
||||||
definition:
|
definition:
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
|||||||
@@ -172,6 +172,31 @@ Parameters
|
|||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: namespace</div>
|
<div style="font-size: small; color: darkgreen"><br/>aliases: namespace</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>release_state</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>Show releases as per their states.</div>
|
||||||
|
<div>Default value is <code>deployed</code> and <code>failed</code>.</div>
|
||||||
|
<div>If set to <code>all</code>, show all releases without any filter applied.</div>
|
||||||
|
<div>If set to <code>deployed</code>, show deployed releases.</div>
|
||||||
|
<div>If set to <code>failed</code>, show failed releases.</div>
|
||||||
|
<div>If set to <code>pending</code>, show pending releases.</div>
|
||||||
|
<div>If set to <code>superseded</code>, show superseded releases.</div>
|
||||||
|
<div>If set to <code>uninstalled</code>, show uninstalled releases, if <code>helm uninstall --keep-history</code> was used.</div>
|
||||||
|
<div>If set to <code>uninstalling</code>, show releases that are currently being uninstalled.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -204,11 +229,18 @@ Examples
|
|||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
- name: Deploy latest version of Grafana chart inside monitoring namespace
|
- name: Gather information 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
|
||||||
|
kubernetes.core.helm_info:
|
||||||
|
name: test-chart
|
||||||
|
release_namespace: testenv
|
||||||
|
release_state:
|
||||||
|
- pending
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Return Values
|
Return Values
|
||||||
|
|||||||
@@ -413,6 +413,24 @@ 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>
|
||||||
@@ -429,7 +447,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.</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 (see Examples).</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -490,7 +508,8 @@ Parameters
|
|||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<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>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>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>
|
||||||
@@ -506,12 +525,19 @@ 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>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
- The default idempotency check can fail to report changes when ``release_state`` is set to ``present`` and ``chart_repo_url`` is defined. Install helm diff >= 3.4.1 for better results.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
@@ -561,6 +587,13 @@ 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:
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ Parameters
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>Name of Helm plugin.</div>
|
<div>Name of Helm plugin.</div>
|
||||||
<div>Required only if <code>state=absent</code>.</div>
|
<div>Required only if <code>state=absent</code> or <code>state=latest</code>.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -170,6 +170,23 @@ Parameters
|
|||||||
<div>Required only if <code>state=present</code>.</div>
|
<div>Required only if <code>state=present</code>.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>plugin_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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Plugin version to install. If this is not specified, the latest version is installed.</div>
|
||||||
|
<div>Ignored when <code>state=absent</code> or <code>state=latest</code>.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -183,10 +200,12 @@ Parameters
|
|||||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
||||||
<li>absent</li>
|
<li>absent</li>
|
||||||
<li><div style="color: blue"><b>present</b> ←</div></li>
|
<li><div style="color: blue"><b>present</b> ←</div></li>
|
||||||
|
<li>latest</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>If <code>state=present</code> the Helm plugin will be installed.</div>
|
<div>If <code>state=present</code> the Helm plugin will be installed.</div>
|
||||||
|
<div>If <code>state=latest</code> the Helm plugin will be updated. Added in version 2.3.0.</div>
|
||||||
<div>If <code>state=absent</code> the Helm plugin will be removed.</div>
|
<div>If <code>state=absent</code> the Helm plugin will be removed.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -237,6 +256,17 @@ Examples
|
|||||||
plugin_name: env
|
plugin_name: env
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
|
- name: Install Helm plugin with a specific version
|
||||||
|
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 Values
|
Return Values
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ Parameters
|
|||||||
<th colspan="1">Parameter</th>
|
<th colspan="1">Parameter</th>
|
||||||
<th>Choices/<font color="blue">Defaults</font></th>
|
<th>Choices/<font color="blue">Defaults</font></th>
|
||||||
<th width="100%">Comments</th>
|
<th width="100%">Comments</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Token used to authenticate with the API. Can also be specified via <code>K8S_AUTH_API_KEY</code> environment variable.</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
@@ -55,6 +71,59 @@ Parameters
|
|||||||
<div>The path of a helm binary to use.</div>
|
<div>The path of a helm binary to use.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</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 <code>K8S_AUTH_SSL_CA_CERT</code> environment variable.</div>
|
||||||
|
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Provide a URL for accessing the API. Can also be specified via <code>K8S_AUTH_HOST</code> environment variable.</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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</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>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -142,6 +211,27 @@ Parameters
|
|||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: username</div>
|
<div style="font-size: small; color: darkgreen"><br/>aliases: username</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
||||||
|
<li>no</li>
|
||||||
|
<li><div style="color: blue"><b>yes</b> ←</div></li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>Whether or not to verify the API server's SSL certificates. Can also be specified via <code>K8S_AUTH_VERIFY_SSL</code> environment variable.</div>
|
||||||
|
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,22 @@ Parameters
|
|||||||
<div>If the directory already exists, it will be overwritten.</div>
|
<div>If the directory already exists, it will be overwritten.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>release_namespace</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>namespace scope for this request.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -148,6 +164,23 @@ Parameters
|
|||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: values</div>
|
<div style="font-size: small; color: darkgreen"><br/>aliases: values</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>show_only</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>Only show manifests rendered from the given templates.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -213,6 +246,24 @@ Examples
|
|||||||
dest: myfile.yaml
|
dest: myfile.yaml
|
||||||
content: "{{ result.stdout }}"
|
content: "{{ result.stdout }}"
|
||||||
|
|
||||||
|
- name: Render MutatingWebhooksConfiguration for revision tag "canary", rev "1-13-0"
|
||||||
|
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 Values
|
Return Values
|
||||||
|
|||||||
@@ -136,6 +136,41 @@ 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>
|
||||||
@@ -171,6 +206,25 @@ Parameters
|
|||||||
<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>
|
<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>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -167,6 +167,41 @@ 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>
|
||||||
@@ -236,6 +271,25 @@ Parameters
|
|||||||
<div>This option is ignored when <em>content</em> is set or when <em>state</em> is set to <code>from_pod</code>.</div>
|
<div>This option is ignored when <em>content</em> is set or when <em>state</em> is set to <code>from_pod</code>.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -137,6 +137,27 @@ Parameters
|
|||||||
<div>Specify options to delete pods.</div>
|
<div>Specify options to delete pods.</div>
|
||||||
<div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
|
<div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</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>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="elbow-placeholder"></td>
|
<td class="elbow-placeholder"></td>
|
||||||
@@ -266,6 +287,41 @@ 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>
|
||||||
@@ -298,6 +354,25 @@ Parameters
|
|||||||
<div>The name of the node.</div>
|
<div>The name of the node.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>The command to execute</div>
|
<div>The command to execute.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -134,6 +134,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
<div>The name of the container in the pod to connect to.</div>
|
<div>The name of the container in the pod to connect to.</div>
|
||||||
<div>Defaults to only container if there is only one container in the pod.</div>
|
<div>Defaults to only container if there is only one container in the pod.</div>
|
||||||
|
<div>If not specified, will choose the first container from the given pod as kubectl cmdline does.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -166,6 +167,41 @@ 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>
|
||||||
@@ -195,7 +231,26 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>The pod namespace name</div>
|
<div>The pod namespace name.</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -250,7 +305,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>The pod name</div>
|
<div>The pod name.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -267,7 +322,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
<div>The URL of an HTTP proxy to use for the connection.</div>
|
<div>The URL of an HTTP proxy to use for the connection.</div>
|
||||||
<div>Can also be specified via <em>K8S_AUTH_PROXY</em> environment variable.</div>
|
<div>Can also be specified via <em>K8S_AUTH_PROXY</em> environment variable.</div>
|
||||||
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
|
<div>Please note that this module does not pick up typical proxy settings from the environment (for example, HTTP_PROXY).</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -414,6 +469,13 @@ Examples
|
|||||||
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
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: myproject
|
||||||
|
pod: busybox-test
|
||||||
|
container: manager
|
||||||
|
command: echo "hello"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Return Values
|
Return Values
|
||||||
|
|||||||
@@ -173,6 +173,41 @@ 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>
|
||||||
@@ -260,6 +295,25 @@ Parameters
|
|||||||
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ Status
|
|||||||
Authors
|
Authors
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
- Chris Houseknecht <@chouseknecht>
|
- Chris Houseknecht (@chouseknecht)
|
||||||
- Fabian von Feilitzsch <@fabianvf>
|
- Fabian von Feilitzsch (@fabianvf)
|
||||||
|
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
|
|||||||
@@ -155,6 +155,41 @@ 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>
|
||||||
@@ -221,6 +256,25 @@ Parameters
|
|||||||
<div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
|
<div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -174,6 +174,41 @@ 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>
|
||||||
@@ -260,6 +295,25 @@ Parameters
|
|||||||
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -438,106 +438,28 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
|
|||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
<table border=0 cellpadding=0 class="documentation-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">Key</th>
|
<th colspan="1">Key</th>
|
||||||
<th>Returned</th>
|
<th>Returned</th>
|
||||||
<th width="100%">Description</th>
|
<th width="100%">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||||
<b>_list</b>
|
<b>_list</b>
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||||
<div style="font-size: small">
|
<div style="font-size: small">
|
||||||
<span style="color: purple">complex</span>
|
<span style="color: purple">list</span>
|
||||||
|
/ <span style="color: purple">elements=dictionary</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<div>One ore more object definitions returned from the API.</div>
|
<div>One ore more object definitions returned from the API.</div>
|
||||||
<br/>
|
<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;">[{'kind': 'ConfigMap', 'apiVersion': 'v1', 'metadata': {'creationTimestamp': '2022-03-04T13:59:49Z', 'name': 'my-config-map', 'namespace': 'default', 'resourceVersion': '418', 'uid': '5714b011-d090-4eac-8272-a0ea82ec0abd'}, 'data': {'key1': 'val1'}}]</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
@@ -549,8 +471,8 @@ Status
|
|||||||
Authors
|
Authors
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
- Chris Houseknecht <@chouseknecht>
|
- Chris Houseknecht (@chouseknecht)
|
||||||
- Fabian von Feilitzsch <@fabianvf>
|
- Fabian von Feilitzsch (@fabianvf)
|
||||||
|
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
|
|||||||
@@ -336,6 +336,26 @@ Parameters
|
|||||||
<div>If set to <code>yes</code>, and <em>state</em> is <code>present</code>, an existing object will be replaced.</div>
|
<div>If set to <code>yes</code>, and <em>state</em> is <code>present</code>, an existing object will be replaced.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>generate_name</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>Use to specify the basis of an object name and random characters will be added automatically on server to generate a unique name.</div>
|
||||||
|
<div>This option is ignored when <em>state</em> is not set to <code>present</code> or when <em>apply</em> is set to <code>yes</code>.</div>
|
||||||
|
<div>If <em>resource definition</em> is provided, the <em>metadata.generateName</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
|
<div>If <em>resource definition</em> is provided, and contains <em>metadata.name</em>, this option is ignored.</div>
|
||||||
|
<div>mutually exclusive with <code>name</code>.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -351,6 +371,41 @@ 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>
|
||||||
@@ -464,6 +519,25 @@ Parameters
|
|||||||
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<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>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -605,6 +679,63 @@ Parameters
|
|||||||
<div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
|
<div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>server_side_apply</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.3.0</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>When this option is set, apply runs in the server instead of the client.</div>
|
||||||
|
<div>Ignored if <code>apply</code> is not set or is set to False.</div>
|
||||||
|
<div>This option requires "kubernetes >= 19.15.0".</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="elbow-placeholder"></td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>field_manager</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>Name of the manager used to track field ownership.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="elbow-placeholder"></td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
<b>force_conflicts</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>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.</div>
|
||||||
|
<div>When set to True, server-side apply will force the changes against conflicts.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -619,7 +750,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
||||||
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
||||||
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
|
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>kubernetes.core.k8s</span> module.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1058,6 +1189,33 @@ Examples
|
|||||||
labels:
|
labels:
|
||||||
support: patch
|
support: patch
|
||||||
|
|
||||||
|
# Create object using generateName
|
||||||
|
- 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 Values
|
Return Values
|
||||||
|
|||||||
@@ -172,6 +172,41 @@ 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>
|
||||||
@@ -258,6 +293,25 @@ Parameters
|
|||||||
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
|
|||||||
@@ -189,6 +189,41 @@ 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>
|
||||||
@@ -276,6 +311,25 @@ Parameters
|
|||||||
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
<div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -462,7 +516,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
||||||
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
||||||
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
|
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>kubernetes.core.k8s</span> module.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -172,6 +172,41 @@ 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>
|
||||||
@@ -245,6 +280,25 @@ Parameters
|
|||||||
<div>Use to specify a Service object namespace.</div>
|
<div>Use to specify a Service object namespace.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||||
@@ -433,7 +487,7 @@ Parameters
|
|||||||
<td>
|
<td>
|
||||||
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
<div>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
|
||||||
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
<div>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 <em>resource_definition</em>. See Examples below.</div>
|
||||||
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
|
<div>Mutually exclusive with <em>template</em> in case of <span class='module'>kubernetes.core.k8s</span> module.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
660
docs/kubernetes.core.k8s_taint_module.rst
Normal file
660
docs/kubernetes.core.k8s_taint_module.rst
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
.. _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)
|
||||||
@@ -354,7 +354,7 @@ Status
|
|||||||
Authors
|
Authors
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
- xuxinkun
|
- xuxinkun (@xuxinkun)
|
||||||
|
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
|
|||||||
@@ -133,106 +133,27 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
|
|||||||
|
|
||||||
<table border=0 cellpadding=0 class="documentation-table">
|
<table border=0 cellpadding=0 class="documentation-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">Key</th>
|
<th colspan="1">Key</th>
|
||||||
<th>Returned</th>
|
<th>Returned</th>
|
||||||
<th width="100%">Description</th>
|
<th width="100%">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="1">
|
||||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||||
<b>_list</b>
|
<b>_list</b>
|
||||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||||
<div style="font-size: small">
|
<div style="font-size: small">
|
||||||
<span style="color: purple">complex</span>
|
<span style="color: purple">string</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<div>One ore more object definitions returned from the tool execution.</div>
|
<div>YAML string for the object definitions returned from the tool execution.</div>
|
||||||
<br/>
|
<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;">{'kind': 'ConfigMap', 'apiVersion': 'v1', 'metadata': {'name': 'my-config-map', 'namespace': 'default'}, 'data': {'key1': 'val1'}}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
@@ -244,7 +165,7 @@ Status
|
|||||||
Authors
|
Authors
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
- Aubin Bikouo <@abikouo>
|
- Aubin Bikouo (@abikouo)
|
||||||
|
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ tags:
|
|||||||
- openshift
|
- openshift
|
||||||
- okd
|
- okd
|
||||||
- cluster
|
- cluster
|
||||||
version: 2.2.0
|
version: 2.3.2
|
||||||
build_ignore:
|
build_ignore:
|
||||||
- .DS_Store
|
- .DS_Store
|
||||||
- '*.tar.gz'
|
- '*.tar.gz'
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ action_groups:
|
|||||||
k8s:
|
k8s:
|
||||||
- k8s
|
- k8s
|
||||||
- k8s_exec
|
- k8s_exec
|
||||||
- k8s_facts
|
|
||||||
- k8s_info
|
- k8s_info
|
||||||
- k8s_log
|
- k8s_log
|
||||||
- k8s_scale
|
- k8s_scale
|
||||||
@@ -18,37 +17,6 @@ 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
|
||||||
|
|||||||
@@ -1,292 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Converge
|
|
||||||
hosts: localhost
|
|
||||||
connection: local
|
|
||||||
|
|
||||||
collections:
|
|
||||||
- kubernetes.core
|
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/main.yml
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Verify cluster is working.
|
|
||||||
k8s_info:
|
|
||||||
namespace: kube-system
|
|
||||||
kind: Pod
|
|
||||||
register: pod_list
|
|
||||||
|
|
||||||
- name: Verify cluster has more than 5 pods running.
|
|
||||||
assert:
|
|
||||||
that: (pod_list.resources | count) > 5
|
|
||||||
|
|
||||||
- name: Include access_review.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/access_review.yml
|
|
||||||
apply:
|
|
||||||
tags: [ access_review, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include append_hash.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/append_hash.yml
|
|
||||||
apply:
|
|
||||||
tags: [ append_hash, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include apply.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/apply.yml
|
|
||||||
apply:
|
|
||||||
tags: [ apply, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include cluster_info.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/cluster_info.yml
|
|
||||||
apply:
|
|
||||||
tags: [ cluster_info, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include crd.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/crd.yml
|
|
||||||
apply:
|
|
||||||
tags: [ crd, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include delete.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/delete.yml
|
|
||||||
apply:
|
|
||||||
tags: [ delete, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include exec.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/exec.yml
|
|
||||||
apply:
|
|
||||||
tags: [ exec, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include full.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/full.yml
|
|
||||||
apply:
|
|
||||||
tags: [ full, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include gc.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/gc.yml
|
|
||||||
apply:
|
|
||||||
tags: [ gc, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include info.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/info.yml
|
|
||||||
apply:
|
|
||||||
tags: [ info, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include json_patch.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/json_patch.yml
|
|
||||||
apply:
|
|
||||||
tags: [ json_patch, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include lists.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/lists.yml
|
|
||||||
apply:
|
|
||||||
tags: [ lists, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include log.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/log.yml
|
|
||||||
apply:
|
|
||||||
tags: [ log, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include rollback.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/rollback.yml
|
|
||||||
apply:
|
|
||||||
tags: [ rollback, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include scale.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/scale.yml
|
|
||||||
apply:
|
|
||||||
tags: [ scale, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
- name: Include template.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/template.yml
|
|
||||||
apply:
|
|
||||||
tags: [ template, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include validate.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/validate.yml
|
|
||||||
apply:
|
|
||||||
tags: [ validate, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include waiter.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/waiter.yml
|
|
||||||
apply:
|
|
||||||
tags: [ waiter, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include merge_type.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/merge_type.yml
|
|
||||||
apply:
|
|
||||||
tags: [ merge_type, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include patched.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/patched.yml
|
|
||||||
apply:
|
|
||||||
tags: [ patched, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include lookup_k8s.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/lookup_k8s.yml
|
|
||||||
apply:
|
|
||||||
tags: [ lookup_k8s, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include label_selectors.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/label_selectors.yml
|
|
||||||
apply:
|
|
||||||
tags: [ label_selectors, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include diff.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/diff.yml
|
|
||||||
apply:
|
|
||||||
tags: [ diff, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
- name: Include lookup_kustomize.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/lookup_kustomize.yml
|
|
||||||
apply:
|
|
||||||
tags: [ lookup_kustomize, k8s ]
|
|
||||||
tags:
|
|
||||||
- always
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: helm
|
|
||||||
tags:
|
|
||||||
- helm
|
|
||||||
|
|
||||||
- role: k8scopy
|
|
||||||
tags:
|
|
||||||
- copy
|
|
||||||
- k8s
|
|
||||||
|
|
||||||
post_tasks:
|
|
||||||
- name: Ensure namespace exists
|
|
||||||
k8s:
|
|
||||||
api_version: v1
|
|
||||||
kind: Namespace
|
|
||||||
name: inventory
|
|
||||||
|
|
||||||
- name: Add a deployment
|
|
||||||
k8s:
|
|
||||||
definition:
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: inventory
|
|
||||||
namespace: inventory
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: "{{ k8s_pod_name }}"
|
|
||||||
template: "{{ k8s_pod_template }}"
|
|
||||||
wait: yes
|
|
||||||
wait_timeout: 120
|
|
||||||
vars:
|
|
||||||
k8s_pod_name: inventory
|
|
||||||
k8s_pod_image: python
|
|
||||||
k8s_pod_command:
|
|
||||||
- python
|
|
||||||
- '-m'
|
|
||||||
- http.server
|
|
||||||
k8s_pod_env:
|
|
||||||
- name: TEST
|
|
||||||
value: test
|
|
||||||
|
|
||||||
- meta: refresh_inventory
|
|
||||||
|
|
||||||
- name: Verify inventory and connection plugins
|
|
||||||
hosts: namespace_inventory_pods
|
|
||||||
gather_facts: no
|
|
||||||
|
|
||||||
vars:
|
|
||||||
file_content: |
|
|
||||||
Hello world
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: End play if host not running (TODO should we not add these to the inventory?)
|
|
||||||
meta: end_host
|
|
||||||
when: pod_phase != "Running"
|
|
||||||
|
|
||||||
- debug: var=hostvars
|
|
||||||
- setup:
|
|
||||||
|
|
||||||
- debug: var=ansible_facts
|
|
||||||
|
|
||||||
- name: Assert the TEST environment variable was retrieved
|
|
||||||
assert:
|
|
||||||
that: ansible_facts.env.TEST == 'test'
|
|
||||||
|
|
||||||
- name: Copy a file into the host
|
|
||||||
copy:
|
|
||||||
content: '{{ file_content }}'
|
|
||||||
dest: /tmp/test_file
|
|
||||||
|
|
||||||
- name: Retrieve the file from the host
|
|
||||||
slurp:
|
|
||||||
src: /tmp/test_file
|
|
||||||
register: slurped_file
|
|
||||||
|
|
||||||
- name: Assert the file content matches expectations
|
|
||||||
assert:
|
|
||||||
that: (slurped_file.content|b64decode) == file_content
|
|
||||||
|
|
||||||
- name: Delete inventory namespace
|
|
||||||
hosts: localhost
|
|
||||||
connection: local
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: Remove inventory namespace
|
|
||||||
k8s:
|
|
||||||
api_version: v1
|
|
||||||
kind: Namespace
|
|
||||||
name: inventory
|
|
||||||
state: absent
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
driver:
|
|
||||||
name: delegated
|
|
||||||
options:
|
|
||||||
managed: false
|
|
||||||
login_cmd_template: 'docker exec -ti {instance} bash'
|
|
||||||
ansible_connection_options:
|
|
||||||
ansible_connection: docker
|
|
||||||
platforms:
|
|
||||||
- name: instance-kind
|
|
||||||
provisioner:
|
|
||||||
name: ansible
|
|
||||||
log: true
|
|
||||||
config_options:
|
|
||||||
inventory:
|
|
||||||
enable_plugins: kubernetes.core.k8s,yaml
|
|
||||||
lint: {}
|
|
||||||
inventory:
|
|
||||||
hosts:
|
|
||||||
plugin: kubernetes.core.k8s
|
|
||||||
host_vars:
|
|
||||||
localhost:
|
|
||||||
ansible_python_interpreter: '{{ ansible_playbook_python }}'
|
|
||||||
env:
|
|
||||||
ANSIBLE_FORCE_COLOR: 'true'
|
|
||||||
options:
|
|
||||||
vvv: True
|
|
||||||
scenario:
|
|
||||||
name: default
|
|
||||||
test_sequence:
|
|
||||||
- dependency
|
|
||||||
- syntax
|
|
||||||
- prepare
|
|
||||||
- converge
|
|
||||||
- verify
|
|
||||||
dependency:
|
|
||||||
name: galaxy
|
|
||||||
options:
|
|
||||||
requirements-file: requirements.yml
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Prepare
|
|
||||||
hosts: localhost
|
|
||||||
connection: local
|
|
||||||
|
|
||||||
collections:
|
|
||||||
- kubernetes.core
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Include drain.yml
|
|
||||||
include_tasks:
|
|
||||||
file: tasks/drain.yml
|
|
||||||
Binary file not shown.
@@ -1,217 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: (c) 2021, Aubin Bikouo <@abikouo>
|
|
||||||
# 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: kubectl_file_compare
|
|
||||||
|
|
||||||
short_description: Compare file and directory using kubectl
|
|
||||||
|
|
||||||
author:
|
|
||||||
- Aubin Bikouo (@abikouo)
|
|
||||||
|
|
||||||
description:
|
|
||||||
- This module is used to validate k8s_cp module.
|
|
||||||
- Compare the local file/directory with the remote pod version
|
|
||||||
|
|
||||||
notes:
|
|
||||||
- This module authenticates on kubernetes cluster using default kubeconfig only.
|
|
||||||
|
|
||||||
options:
|
|
||||||
namespace:
|
|
||||||
description:
|
|
||||||
- The pod namespace name
|
|
||||||
type: str
|
|
||||||
required: yes
|
|
||||||
pod:
|
|
||||||
description:
|
|
||||||
- The pod name
|
|
||||||
type: str
|
|
||||||
required: yes
|
|
||||||
container:
|
|
||||||
description:
|
|
||||||
- The container to retrieve files from.
|
|
||||||
type: str
|
|
||||||
remote_path:
|
|
||||||
description:
|
|
||||||
- Path of the file or directory on Pod.
|
|
||||||
type: path
|
|
||||||
required: yes
|
|
||||||
local_path:
|
|
||||||
description:
|
|
||||||
- Path of the local file or directory.
|
|
||||||
type: path
|
|
||||||
content:
|
|
||||||
description:
|
|
||||||
- local content to compare with remote file from pod.
|
|
||||||
- mutually exclusive with option I(local_path).
|
|
||||||
type: path
|
|
||||||
required: yes
|
|
||||||
args:
|
|
||||||
description:
|
|
||||||
- The file is considered to be an executable.
|
|
||||||
- The tool will be run locally and on pod and compare result from output and stderr.
|
|
||||||
type: list
|
|
||||||
kubectl_path:
|
|
||||||
description:
|
|
||||||
- Path to the kubectl executable, if not specified it will be download.
|
|
||||||
type: path
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: compare local /tmp/foo with /tmp/bar in a remote pod
|
|
||||||
kubectl_file_compare:
|
|
||||||
namespace: some-namespace
|
|
||||||
pod: some-pod
|
|
||||||
remote_path: /tmp/bar
|
|
||||||
local_path: /tmp/foo
|
|
||||||
kubectl_path: /tmp/test/kubectl
|
|
||||||
|
|
||||||
- name: Compare executable running help command
|
|
||||||
kubectl_file_compare:
|
|
||||||
namespace: some-namespace
|
|
||||||
pod: some-pod
|
|
||||||
remote_path: /tmp/test/kubectl
|
|
||||||
local_path: kubectl
|
|
||||||
kubectl_path: /tmp/test/kubectl
|
|
||||||
args:
|
|
||||||
- "--help"
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
RETURN = r'''
|
|
||||||
'''
|
|
||||||
|
|
||||||
import os
|
|
||||||
import filecmp
|
|
||||||
|
|
||||||
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
|
|
||||||
def kubectl_get_content(module, dest_dir):
|
|
||||||
kubectl_path = module.params.get('kubectl_path')
|
|
||||||
if kubectl_path is None:
|
|
||||||
kubectl_path = module.get_bin_path('kubectl', required=True)
|
|
||||||
|
|
||||||
namespace = module.params.get('namespace')
|
|
||||||
pod = module.params.get('pod')
|
|
||||||
file = module.params.get('remote_path')
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
kubectl_path,
|
|
||||||
'cp',
|
|
||||||
"{0}/{1}:{2}".format(namespace, pod, file)
|
|
||||||
]
|
|
||||||
container = module.params.get('container')
|
|
||||||
if container:
|
|
||||||
cmd += ['-c', container]
|
|
||||||
local_file = os.path.join(dest_dir, os.path.basename(module.params.get('remote_path')))
|
|
||||||
cmd.append(local_file)
|
|
||||||
rc, out, err = module.run_command(cmd)
|
|
||||||
return local_file, err, rc, out
|
|
||||||
|
|
||||||
|
|
||||||
def kubectl_run_from_pod(module):
|
|
||||||
kubectl_path = module.params.get('kubectl_path')
|
|
||||||
if kubectl_path is None:
|
|
||||||
kubectl_path = module.get_bin_path('kubectl', required=True)
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
kubectl_path,
|
|
||||||
'exec',
|
|
||||||
module.params.get('pod'),
|
|
||||||
'-n',
|
|
||||||
module.params.get('namespace')
|
|
||||||
]
|
|
||||||
container = module.params.get('container')
|
|
||||||
if container:
|
|
||||||
cmd += ['-c', container]
|
|
||||||
cmd += ['--', module.params.get('remote_path')]
|
|
||||||
cmd += module.params.get('args')
|
|
||||||
return module.run_command(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def compare_directories(dir1, dir2):
|
|
||||||
test = filecmp.dircmp(dir1, dir2)
|
|
||||||
if any([len(test.left_only) > 0, len(test.right_only) > 0, len(test.funny_files) > 0]):
|
|
||||||
return False
|
|
||||||
(t, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, test.common_files, shallow=False)
|
|
||||||
if len(mismatch) > 0 or len(errors) > 0:
|
|
||||||
return False
|
|
||||||
for common_dir in test.common_dirs:
|
|
||||||
new_dir1 = os.path.join(dir1, common_dir)
|
|
||||||
new_dir2 = os.path.join(dir2, common_dir)
|
|
||||||
if not compare_directories(new_dir1, new_dir2):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module):
|
|
||||||
|
|
||||||
args = module.params.get('args')
|
|
||||||
local_path = module.params.get('local_path')
|
|
||||||
namespace = module.params.get('namespace')
|
|
||||||
pod = module.params.get('pod')
|
|
||||||
file = module.params.get('remote_path')
|
|
||||||
content = module.params.get('content')
|
|
||||||
if args:
|
|
||||||
pod_rc, pod_out, pod_err = kubectl_run_from_pod(module)
|
|
||||||
rc, out, err = module.run_command([module.params.get('local_path')] + args)
|
|
||||||
if rc == pod_rc and out == pod_out:
|
|
||||||
module.exit_json(msg="{0} and {1}/{2}:{3} are same.".format(
|
|
||||||
local_path, namespace, pod, file
|
|
||||||
), rc=rc, stderr=err, stdout=out)
|
|
||||||
result = dict(local=dict(rc=rc, out=out, err=err), remote=dict(rc=pod_rc, out=pod_out, err=pod_err))
|
|
||||||
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.", **result)
|
|
||||||
else:
|
|
||||||
with TemporaryDirectory() as tmpdirname:
|
|
||||||
file_from_pod, err, rc, out = kubectl_get_content(module=module, dest_dir=tmpdirname)
|
|
||||||
if not os.path.exists(file_from_pod):
|
|
||||||
module.fail_json(msg="failed to copy content from pod", error=err, output=out)
|
|
||||||
|
|
||||||
if content is not None:
|
|
||||||
with NamedTemporaryFile(mode="w") as tmp_file:
|
|
||||||
tmp_file.write(content)
|
|
||||||
tmp_file.flush()
|
|
||||||
if filecmp.cmp(file_from_pod, tmp_file.name):
|
|
||||||
module.exit_json(msg=f"defined content and {namespace}/{pod}:{file} are same.")
|
|
||||||
module.fail_json(msg=f"defined content and {namespace}/{pod}:{file} are same.")
|
|
||||||
|
|
||||||
if os.path.isfile(local_path):
|
|
||||||
if filecmp.cmp(file_from_pod, local_path):
|
|
||||||
module.exit_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
|
|
||||||
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
|
|
||||||
|
|
||||||
if os.path.isdir(local_path):
|
|
||||||
if compare_directories(file_from_pod, local_path):
|
|
||||||
module.exit_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
|
|
||||||
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = {}
|
|
||||||
argument_spec['namespace'] = {'type': 'str', 'required': True}
|
|
||||||
argument_spec['pod'] = {'type': 'str', 'required': True}
|
|
||||||
argument_spec['container'] = {}
|
|
||||||
argument_spec['remote_path'] = {'type': 'path', 'required': True}
|
|
||||||
argument_spec['local_path'] = {'type': 'path'}
|
|
||||||
argument_spec['content'] = {'type': 'str'}
|
|
||||||
argument_spec['kubectl_path'] = {'type': 'path'}
|
|
||||||
argument_spec['args'] = {'type': 'list'}
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=[('local_path', 'content')],
|
|
||||||
required_one_of=[['local_path', 'content']])
|
|
||||||
|
|
||||||
execute_module(module)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
---
|
|
||||||
- vars:
|
|
||||||
exec_namespace: k8s-exec
|
|
||||||
pod: sleep-pod
|
|
||||||
exec_pod_definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: "{{ pod }}"
|
|
||||||
namespace: "{{ exec_namespace }}"
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: sleeper
|
|
||||||
image: busybox
|
|
||||||
command: ["sleep", "infinity"]
|
|
||||||
|
|
||||||
block:
|
|
||||||
- name: "Ensure that {{ exec_namespace }} namespace exists"
|
|
||||||
k8s:
|
|
||||||
kind: Namespace
|
|
||||||
name: "{{ exec_namespace }}"
|
|
||||||
|
|
||||||
- name: "Create a pod"
|
|
||||||
k8s:
|
|
||||||
definition: "{{ exec_pod_definition }}"
|
|
||||||
wait: yes
|
|
||||||
wait_sleep: 1
|
|
||||||
wait_timeout: 30
|
|
||||||
|
|
||||||
- name: "Execute a command"
|
|
||||||
k8s_exec:
|
|
||||||
pod: "{{ pod }}"
|
|
||||||
namespace: "{{ exec_namespace }}"
|
|
||||||
command: cat /etc/resolv.conf
|
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: "Show k8s_exec output"
|
|
||||||
debug:
|
|
||||||
var: output
|
|
||||||
|
|
||||||
- name: "Assert k8s_exec output is correct"
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "'nameserver' in output.stdout"
|
|
||||||
|
|
||||||
- name: Check if rc is returned for the given command
|
|
||||||
k8s_exec:
|
|
||||||
namespace: "{{ exec_namespace }}"
|
|
||||||
pod: "{{ pod }}"
|
|
||||||
command: 'false'
|
|
||||||
register: command_status
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- name: Check last command status
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- command_status.rc != 0
|
|
||||||
- command_status.return_code != 0
|
|
||||||
|
|
||||||
always:
|
|
||||||
- name: "Cleanup namespace"
|
|
||||||
k8s:
|
|
||||||
kind: Namespace
|
|
||||||
name: "{{ exec_namespace }}"
|
|
||||||
state: absent
|
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
# 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
|
||||||
@@ -11,15 +12,44 @@ import traceback
|
|||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
from ansible.config.manager import ensure_type
|
from ansible.config.manager import ensure_type
|
||||||
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
|
from ansible.errors import (
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
class ActionModule(ActionBase):
|
||||||
|
|
||||||
TRANSFERS_FILES = True
|
TRANSFERS_FILES = True
|
||||||
@@ -28,19 +58,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))
|
||||||
|
|
||||||
@@ -48,15 +78,19 @@ 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("could not find template=%s, %s" % (source, to_text(e)))
|
raise AnsibleActionFail(
|
||||||
b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict')
|
"could not find template=%s, %s" % (source, to_text(e))
|
||||||
|
)
|
||||||
|
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("Template source files must be utf-8 encoded")
|
raise AnsibleActionFail(
|
||||||
|
"Template source files must be utf-8 encoded"
|
||||||
|
)
|
||||||
yield template_data
|
yield template_data
|
||||||
except AnsibleAction:
|
except AnsibleAction:
|
||||||
raise
|
raise
|
||||||
@@ -73,63 +107,104 @@ 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 ('newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
|
for s_type in (
|
||||||
'block_end_string'):
|
"newline_sequence",
|
||||||
|
"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("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
|
raise AnsibleActionFail(
|
||||||
|
"%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),
|
{
|
||||||
"lstrip_blocks": boolean(template_args.get('lstrip_blocks', False), strict=False)
|
"trim_blocks": boolean(
|
||||||
})
|
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),
|
{
|
||||||
"variable_start_string": template_args.get('variable_start_string', None),
|
"newline_sequence": template_args.get(
|
||||||
"variable_end_string": template_args.get('variable_end_string', None),
|
"newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
|
||||||
"block_start_string": template_args.get('block_start_string', None),
|
),
|
||||||
"block_end_string": template_args.get('block_end_string', None)
|
"variable_start_string": template_args.get(
|
||||||
})
|
"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("Error while reading template file - "
|
raise AnsibleActionFail(
|
||||||
"a string or dict for template expected, but got %s instead" % type(template))
|
"Error while reading template file - "
|
||||||
|
"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('Unable to import Jinja2 defaults for determining Jinja2 features.')
|
raise AnsibleError(
|
||||||
|
"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("Option `lstrip_blocks' is only available in Jinja2 versions >=2.7")
|
raise AnsibleError(
|
||||||
|
"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 ('k8s', 'kubernetes.core.k8s', 'community.okd.k8s', 'redhat.openshift.k8s', 'community.kubernetes.k8s'):
|
if self._task.action not in (
|
||||||
raise AnsibleActionFail("'template' is only a supported parameter for the 'k8s' module.")
|
"k8s",
|
||||||
|
"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))
|
||||||
@@ -137,8 +212,11 @@ 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("Error while reading template file - "
|
raise AnsibleActionFail(
|
||||||
"a string or dict for template expected, but got %s instead" % type(template))
|
"Error while reading template file - "
|
||||||
|
"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)
|
||||||
|
|
||||||
@@ -149,20 +227,31 @@ class ActionModule(ActionBase):
|
|||||||
old_vars = self._templar.available_variables
|
old_vars = self._templar.available_variables
|
||||||
|
|
||||||
default_environment = {}
|
default_environment = {}
|
||||||
for key in ("newline_sequence", "variable_start_string", "variable_end_string",
|
for key in (
|
||||||
"block_start_string", "block_end_string", "trim_blocks", "lstrip_blocks"):
|
"newline_sequence",
|
||||||
|
"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[wrong_sequences.index(newline_sequence)]
|
template_item["newline_sequence"] = allowed_sequences[
|
||||||
|
wrong_sequences.index(newline_sequence)
|
||||||
|
]
|
||||||
elif newline_sequence not in allowed_sequences:
|
elif newline_sequence not in allowed_sequences:
|
||||||
raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")
|
raise AnsibleActionFail(
|
||||||
|
"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):
|
||||||
@@ -170,29 +259,48 @@ 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(self._templar.environment, key, default_environment.get(key))
|
setattr(
|
||||||
|
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(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
|
result = self._templar.do_template(
|
||||||
|
template_data,
|
||||||
|
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)
|
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 ('k8s_cp', 'kubernetes.core.k8s_cp', 'community.kubernetes.k8s_cp'):
|
if self._task.action not in (
|
||||||
raise AnsibleActionFail("'local_path' is only supported parameter for 'k8s_cp' module.")
|
"k8s_cp",
|
||||||
|
"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("%s does not exist in local filesystem" % local_path)
|
raise AnsibleActionFail(
|
||||||
|
"%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):
|
||||||
@@ -200,20 +308,22 @@ 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
|
||||||
kubeconfig = self._find_needle('files', kubeconfig)
|
kubeconfig = self._find_needle("files", kubeconfig)
|
||||||
|
|
||||||
# decrypt kubeconfig found
|
# decrypt kubeconfig found
|
||||||
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
||||||
new_module_args['kubeconfig'] = actual_file
|
new_module_args["kubeconfig"] = actual_file
|
||||||
|
|
||||||
elif isinstance(kubeconfig, dict):
|
elif isinstance(kubeconfig, dict):
|
||||||
new_module_args['kubeconfig'] = kubeconfig
|
new_module_args["kubeconfig"] = kubeconfig
|
||||||
else:
|
else:
|
||||||
raise AnsibleActionFail("Error while reading kubeconfig parameter - "
|
raise AnsibleActionFail(
|
||||||
"a string or dict expected, but got %s instead" % type(kubeconfig))
|
"Error while reading kubeconfig parameter - "
|
||||||
|
"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()
|
||||||
|
|
||||||
@@ -224,53 +334,61 @@ 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:
|
if src:
|
||||||
if remote_transport:
|
if remote_transport:
|
||||||
# src is on remote node
|
# src is on remote node
|
||||||
result.update(self._execute_module(module_name=self._task.action, task_vars=task_vars))
|
result.update(
|
||||||
|
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':
|
if local_path and state == "to_pod" and not remote_transport:
|
||||||
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_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
|
module_return = self._execute_module(
|
||||||
|
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,14 +17,15 @@
|
|||||||
#
|
#
|
||||||
# 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"""
|
||||||
author:
|
author:
|
||||||
- xuxinkun
|
- xuxinkun (@xuxinkun)
|
||||||
|
|
||||||
connection: kubectl
|
name: kubectl
|
||||||
|
|
||||||
short_description: Execute tasks in pods running on Kubernetes.
|
short_description: Execute tasks in pods running on Kubernetes.
|
||||||
|
|
||||||
@@ -170,9 +171,9 @@ DOCUMENTATION = r"""
|
|||||||
aliases: [ kubectl_verify_ssl ]
|
aliases: [ kubectl_verify_ssl ]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import distutils.spawn
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
@@ -185,26 +186,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
|
||||||
@@ -217,57 +218,65 @@ class Connection(ConnectionBase):
|
|||||||
|
|
||||||
# Note: kubectl runs commands as the user that started the container.
|
# Note: kubectl runs commands as the user that started the container.
|
||||||
# It is impossible to set the remote user for a kubectl connection.
|
# It is impossible to set the remote user for a kubectl connection.
|
||||||
cmd_arg = '{0}_command'.format(self.transport)
|
cmd_arg = "{0}_command".format(self.transport)
|
||||||
if cmd_arg in kwargs:
|
self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
|
||||||
self.transport_cmd = kwargs[cmd_arg]
|
|
||||||
else:
|
|
||||||
self.transport_cmd = distutils.spawn.find_executable(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))
|
||||||
|
|
||||||
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(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
local_cmd.append(
|
||||||
censored_local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
"{0}={1}".format(
|
||||||
elif not key.endswith('container') and self.get_option(key) and self.connection_options.get(key):
|
self.connection_options[key], str(skip_verify_ssl).lower()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
censored_local_cmd.append(
|
||||||
|
"{0}={1}".format(
|
||||||
|
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)
|
||||||
|
):
|
||||||
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 = u'{0}_extra_args'.format(self.transport)
|
extra_args_name = "{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(u'{0}_pod'.format(self.transport))
|
pod = self.get_option("{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 = u'{0}_container'.format(self.transport)
|
container_arg_name = "{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
|
||||||
|
|
||||||
@@ -275,25 +284,37 @@ class Connection(ConnectionBase):
|
|||||||
"""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(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._play_context.remote_addr)
|
display.vvv(
|
||||||
|
"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([self._play_context.executable, '-c', cmd])
|
local_cmd, censored_local_cmd = self._build_exec_cmd(
|
||||||
|
[self._play_context.executable, "-c", cmd]
|
||||||
|
)
|
||||||
|
|
||||||
display.vvv("EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr)
|
display.vvv(
|
||||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
"EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr
|
||||||
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
|
)
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
|
||||||
|
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)
|
||||||
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
|
||||||
@@ -301,7 +322,7 @@ class Connection(ConnectionBase):
|
|||||||
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)
|
||||||
@@ -309,61 +330,89 @@ class Connection(ConnectionBase):
|
|||||||
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("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
display.vvv(
|
||||||
|
"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(
|
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
|
||||||
"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([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
|
args, dummy = self._build_exec_cmd(
|
||||||
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(args, stdin=in_file,
|
p = subprocess.Popen(
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
args, stdin=in_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise AnsibleError("kubectl connection requires dd command in the container to put files")
|
raise AnsibleError(
|
||||||
|
"kubectl connection requires dd command in the container to put files"
|
||||||
|
)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
raise AnsibleError(
|
||||||
|
"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("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
display.vvv(
|
||||||
|
"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([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
|
args, dummy = self._build_exec_cmd(
|
||||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
[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]
|
||||||
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(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
|
with open(
|
||||||
|
to_bytes(actual_out_path, errors="surrogate_or_strict"), "wb"
|
||||||
|
) as out_file:
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
p = subprocess.Popen(
|
||||||
stdout=out_file, stderr=subprocess.PIPE)
|
args, stdin=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(self.transport)
|
"{0} connection requires dd command in the container to fetch files".format(
|
||||||
|
self.transport
|
||||||
|
)
|
||||||
)
|
)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
raise AnsibleError(
|
||||||
|
"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(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
os.rename(
|
||||||
|
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"""
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -56,4 +57,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,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -76,6 +77,14 @@ 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.
|
||||||
@@ -110,8 +119,21 @@ 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,13 +5,14 @@
|
|||||||
|
|
||||||
# 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
|
||||||
@@ -48,4 +49,4 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
- Specify the UID of the target object.
|
- Specify the UID of the target object.
|
||||||
'''
|
"""
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -49,4 +50,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,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -28,6 +29,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.
|
||||||
- Mutually exclusive with I(template) in case of M(k8s) module.
|
- Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module.
|
||||||
type: path
|
type: path
|
||||||
'''
|
"""
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -46,4 +47,4 @@ options:
|
|||||||
default: 5
|
default: 5
|
||||||
type: int
|
type: int
|
||||||
version_added: 2.0.0
|
version_added: 2.0.0
|
||||||
'''
|
"""
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -27,4 +28,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,13 +5,14 @@
|
|||||||
|
|
||||||
# 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:
|
||||||
@@ -64,4 +65,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,12 +2,15 @@
|
|||||||
# 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 generate_hash
|
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
|
||||||
|
generate_hash,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def k8s_config_resource_name(resource):
|
def k8s_config_resource_name(resource):
|
||||||
@@ -15,15 +18,14 @@ 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("resource must have a metadata.name key to generate a resource name")
|
raise AnsibleFilterError(
|
||||||
|
"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 {
|
return {"k8s_config_resource_name": k8s_config_resource_name}
|
||||||
'k8s_config_resource_name': k8s_config_resource_name
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
# 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
|
||||||
plugin_type: inventory
|
|
||||||
author:
|
author:
|
||||||
- Chris Houseknecht <@chouseknecht>
|
- Chris Houseknecht (@chouseknecht)
|
||||||
- Fabian von Feilitzsch <@fabianvf>
|
- Fabian von Feilitzsch (@fabianvf)
|
||||||
|
|
||||||
short_description: Kubernetes (K8s) inventory source
|
short_description: Kubernetes (K8s) inventory source
|
||||||
|
|
||||||
@@ -89,9 +89,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,12 +112,17 @@ 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 K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception, get_api_client
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
HAS_K8S_MODULE_HELPER,
|
||||||
|
k8s_import_exception,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -128,13 +133,13 @@ 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):
|
||||||
@@ -142,10 +147,10 @@ class K8sInventoryException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin):
|
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)
|
||||||
@@ -154,11 +159,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
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(k8s_import_exception)
|
"This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
|
||||||
|
k8s_import_exception
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
source_data = None
|
source_data = None
|
||||||
@@ -179,11 +186,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
|
|
||||||
for connection in connections:
|
for connection in connections:
|
||||||
if not isinstance(connection, dict):
|
if not isinstance(connection, dict):
|
||||||
raise K8sInventoryException("Expecting connection to be a dictionary.")
|
raise K8sInventoryException(
|
||||||
|
"Expecting connection to be a dictionary."
|
||||||
|
)
|
||||||
client = get_api_client(**connection)
|
client = get_api_client(**connection)
|
||||||
name = connection.get('name', self.get_default_host_name(client.configuration.host))
|
name = connection.get(
|
||||||
if connection.get('namespaces'):
|
"name", self.get_default_host_name(client.configuration.host)
|
||||||
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:
|
||||||
@@ -199,27 +210,36 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_host_name(host):
|
def get_default_host_name(host):
|
||||||
return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_')
|
return (
|
||||||
|
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('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc))
|
raise K8sInventoryException(
|
||||||
|
"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('Error fetching Pod list: %s' % format_dynamic_api_exc(exc))
|
raise K8sInventoryException(
|
||||||
|
"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)
|
||||||
@@ -230,12 +250,14 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
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 = {} if not pod.metadata.annotations else dict(pod.metadata.annotations)
|
pod_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)
|
||||||
@@ -248,7 +270,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
|
|
||||||
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:
|
||||||
@@ -256,46 +278,85 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
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(container_name, 'annotations', pod_annotations)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName)
|
container_name, "annotations", pod_annotations
|
||||||
self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName)
|
)
|
||||||
self.inventory.set_variable(container_name, 'pod_name', pod.spec.name)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP)
|
container_name, "cluster_name", pod.metadata.clusterName
|
||||||
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(
|
||||||
self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink)
|
container_name, "pod_node_name", pod.spec.nodeName
|
||||||
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, "pod_name", pod.spec.name)
|
||||||
self.inventory.set_variable(container_name, 'container_name', container.image)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(container_name, 'container_image', container.image)
|
container_name, "pod_host_ip", pod.status.hostIP
|
||||||
|
)
|
||||||
|
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(container_name, 'container_state', 'Running')
|
self.inventory.set_variable(
|
||||||
|
container_name, "container_state", "Running"
|
||||||
|
)
|
||||||
if container.state.terminated:
|
if container.state.terminated:
|
||||||
self.inventory.set_variable(container_name, 'container_state', 'Terminated')
|
self.inventory.set_variable(
|
||||||
|
container_name, "container_state", "Terminated"
|
||||||
|
)
|
||||||
if container.state.waiting:
|
if container.state.waiting:
|
||||||
self.inventory.set_variable(container_name, 'container_state', 'Waiting')
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(container_name, 'container_ready', container.ready)
|
container_name, "container_state", "Waiting"
|
||||||
self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/')
|
)
|
||||||
self.inventory.set_variable(container_name, 'ansible_connection', self.connection_plugin)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport),
|
container_name, "container_ready", container.ready
|
||||||
pod_name)
|
)
|
||||||
self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport),
|
self.inventory.set_variable(
|
||||||
container.name)
|
container_name, "ansible_remote_tmp", "/tmp/"
|
||||||
self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport),
|
)
|
||||||
namespace)
|
self.inventory.set_variable(
|
||||||
|
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('Error fetching Service list: %s' % format_dynamic_api_exc(exc))
|
raise K8sInventoryException(
|
||||||
|
"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)
|
||||||
@@ -305,15 +366,21 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
|
|
||||||
for service in obj.items:
|
for service in obj.items:
|
||||||
service_name = service.metadata.name
|
service_name = service.metadata.name
|
||||||
service_labels = {} if not service.metadata.labels else dict(service.metadata.labels)
|
service_labels = (
|
||||||
service_annotations = {} if not service.metadata.annotations else dict(service.metadata.annotations)
|
{} if not service.metadata.labels else dict(service.metadata.labels)
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -322,42 +389,75 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
|||||||
except AnsibleError:
|
except AnsibleError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
ports = [{'name': port.name,
|
ports = [
|
||||||
'port': port.port,
|
{
|
||||||
'protocol': port.protocol,
|
"name": port.name,
|
||||||
'targetPort': port.targetPort,
|
"port": port.port,
|
||||||
'nodePort': port.nodePort} for port in service.spec.ports or []]
|
"protocol": port.protocol,
|
||||||
|
"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(service_name, 'annotations', service_annotations)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName)
|
service_name, "annotations", service_annotations
|
||||||
self.inventory.set_variable(service_name, 'ports', ports)
|
)
|
||||||
self.inventory.set_variable(service_name, 'type', service.spec.type)
|
self.inventory.set_variable(
|
||||||
self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink)
|
service_name, "cluster_name", service.metadata.clusterName
|
||||||
self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion)
|
)
|
||||||
self.inventory.set_variable(service_name, 'uid', service.metadata.uid)
|
self.inventory.set_variable(service_name, "ports", ports)
|
||||||
|
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(service_name, 'external_traffic_policy',
|
self.inventory.set_variable(
|
||||||
service.spec.externalTrafficPolicy)
|
service_name,
|
||||||
|
"external_traffic_policy",
|
||||||
|
service.spec.externalTrafficPolicy,
|
||||||
|
)
|
||||||
if service.spec.externalIPs:
|
if service.spec.externalIPs:
|
||||||
self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs)
|
self.inventory.set_variable(
|
||||||
|
service_name, "external_ips", service.spec.externalIPs
|
||||||
|
)
|
||||||
|
|
||||||
if service.spec.externalName:
|
if service.spec.externalName:
|
||||||
self.inventory.set_variable(service_name, 'external_name', service.spec.externalName)
|
self.inventory.set_variable(
|
||||||
|
service_name, "external_name", service.spec.externalName
|
||||||
|
)
|
||||||
|
|
||||||
if service.spec.healthCheckNodePort:
|
if service.spec.healthCheckNodePort:
|
||||||
self.inventory.set_variable(service_name, 'health_check_node_port',
|
self.inventory.set_variable(
|
||||||
service.spec.healthCheckNodePort)
|
service_name,
|
||||||
|
"health_check_node_port",
|
||||||
|
service.spec.healthCheckNodePort,
|
||||||
|
)
|
||||||
if service.spec.loadBalancerIP:
|
if service.spec.loadBalancerIP:
|
||||||
self.inventory.set_variable(service_name, 'load_balancer_ip',
|
self.inventory.set_variable(
|
||||||
service.spec.loadBalancerIP)
|
service_name, "load_balancer_ip", service.spec.loadBalancerIP
|
||||||
|
)
|
||||||
if service.spec.selector:
|
if service.spec.selector:
|
||||||
self.inventory.set_variable(service_name, 'selector', dict(service.spec.selector))
|
self.inventory.set_variable(
|
||||||
|
service_name, "selector", dict(service.spec.selector)
|
||||||
|
)
|
||||||
|
|
||||||
if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress:
|
if (
|
||||||
load_balancer = [{'hostname': ingress.hostname,
|
hasattr(service.status.loadBalancer, "ingress")
|
||||||
'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress]
|
and 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,18 +3,18 @@
|
|||||||
#
|
#
|
||||||
# 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 = """
|
||||||
lookup: k8s
|
name: k8s
|
||||||
|
|
||||||
short_description: Query the K8s API
|
short_description: Query the K8s API
|
||||||
|
|
||||||
author:
|
author:
|
||||||
- Chris Houseknecht <@chouseknecht>
|
- Chris Houseknecht (@chouseknecht)
|
||||||
- Fabian von Feilitzsch <@fabianvf>
|
- Fabian von Feilitzsch (@fabianvf)
|
||||||
|
|
||||||
description:
|
description:
|
||||||
- Uses the Kubernetes Python client to fetch a specific object by name, all matching objects within a
|
- Uses the Kubernetes Python client to fetch a specific object by name, all matching objects within a
|
||||||
@@ -117,7 +117,7 @@ DOCUMENTATION = '''
|
|||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
- "kubernetes >= 12.0.0"
|
- "kubernetes >= 12.0.0"
|
||||||
- "PyYAML >= 3.11"
|
- "PyYAML >= 3.11"
|
||||||
'''
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Fetch a list of namespaces
|
- name: Fetch a list of namespaces
|
||||||
@@ -159,39 +159,50 @@ RETURN = """
|
|||||||
_list:
|
_list:
|
||||||
description:
|
description:
|
||||||
- One ore more object definitions returned from the API.
|
- One ore more object definitions returned from the API.
|
||||||
type: complex
|
type: list
|
||||||
contains:
|
elements: dict
|
||||||
api_version:
|
sample:
|
||||||
description: The versioned schema of this representation of an object.
|
- kind: ConfigMap
|
||||||
returned: success
|
apiVersion: v1
|
||||||
type: str
|
|
||||||
kind:
|
|
||||||
description: Represents the REST resource this object represents.
|
|
||||||
returned: success
|
|
||||||
type: str
|
|
||||||
metadata:
|
metadata:
|
||||||
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
|
creationTimestamp: "2022-03-04T13:59:49Z"
|
||||||
returned: success
|
name: my-config-map
|
||||||
type: complex
|
namespace: default
|
||||||
spec:
|
resourceVersion: "418"
|
||||||
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
|
uid: 5714b011-d090-4eac-8272-a0ea82ec0abd
|
||||||
returned: success
|
data:
|
||||||
type: complex
|
key1: val1
|
||||||
status:
|
|
||||||
description: Current status details for the object.
|
|
||||||
returned: success
|
|
||||||
type: complex
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
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.plugins.lookup import LookupBase
|
from ansible.module_utils.common.validation import check_type_bool
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
||||||
@@ -200,12 +211,13 @@ except ImportError as e:
|
|||||||
|
|
||||||
|
|
||||||
class KubernetesLookup(K8sAnsibleMixin):
|
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(k8s_import_exception)
|
"Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
|
||||||
|
k8s_import_exception
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.kind = None
|
self.kind = None
|
||||||
@@ -226,31 +238,33 @@ class KubernetesLookup(K8sAnsibleMixin):
|
|||||||
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.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:
|
||||||
resource_definition = self.load_resource_definitions(src)[0]
|
resource_definition = self.load_resource_definitions(src)[0]
|
||||||
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('namespace', self.namespace)
|
self.namespace = resource_definition.get("metadata", {}).get(
|
||||||
|
"namespace", self.namespace
|
||||||
|
)
|
||||||
|
|
||||||
if not self.kind:
|
if not self.kind:
|
||||||
raise AnsibleError(
|
raise AnsibleError(
|
||||||
@@ -260,17 +274,23 @@ class KubernetesLookup(K8sAnsibleMixin):
|
|||||||
|
|
||||||
resource = self.find_resource(self.kind, self.api_version, fail=True)
|
resource = self.find_resource(self.kind, self.api_version, fail=True)
|
||||||
try:
|
try:
|
||||||
k8s_obj = resource.get(name=self.name, namespace=self.namespace, label_selector=self.label_selector, field_selector=self.field_selector)
|
k8s_obj = resource.get(
|
||||||
|
name=self.name,
|
||||||
|
namespace=self.namespace,
|
||||||
|
label_selector=self.label_selector,
|
||||||
|
field_selector=self.field_selector,
|
||||||
|
)
|
||||||
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):
|
||||||
def run(self, terms, variables=None, **kwargs):
|
|
||||||
return KubernetesLookup().run(terms, variables=variables, **kwargs)
|
return KubernetesLookup().run(terms, variables=variables, **kwargs)
|
||||||
|
|
||||||
|
run = _run if not hasattr(LookupBase, "run_on_daemon") else LookupBase.run_on_daemon
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
#
|
#
|
||||||
# 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)
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = """
|
||||||
lookup: kustomize
|
name: kustomize
|
||||||
|
|
||||||
short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file.
|
short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file.
|
||||||
|
|
||||||
version_added: "2.2.0"
|
version_added: "2.2.0"
|
||||||
|
|
||||||
author:
|
author:
|
||||||
- Aubin Bikouo <@abikouo>
|
- Aubin Bikouo (@abikouo)
|
||||||
notes:
|
notes:
|
||||||
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
|
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
|
||||||
description:
|
description:
|
||||||
@@ -33,7 +33,7 @@ DOCUMENTATION = '''
|
|||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 3.6"
|
- "python >= 3.6"
|
||||||
'''
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
- name: Run lookup using kustomize
|
- name: Run lookup using kustomize
|
||||||
@@ -52,29 +52,16 @@ EXAMPLES = """
|
|||||||
RETURN = """
|
RETURN = """
|
||||||
_list:
|
_list:
|
||||||
description:
|
description:
|
||||||
- One ore more object definitions returned from the tool execution.
|
- YAML string for the object definitions returned from the tool execution.
|
||||||
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
|
type: str
|
||||||
|
sample:
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
metadata:
|
metadata:
|
||||||
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
|
name: my-config-map
|
||||||
returned: success
|
namespace: default
|
||||||
type: complex
|
data:
|
||||||
spec:
|
key1: val1
|
||||||
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
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ansible.errors import AnsibleLookupError
|
from ansible.errors import AnsibleLookupError
|
||||||
@@ -91,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:
|
||||||
@@ -104,30 +91,41 @@ def run_command(command):
|
|||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
class LookupModule(LookupBase):
|
||||||
|
def run(
|
||||||
def run(self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs):
|
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(name="kubectl", opt_dirs=opt_dirs)
|
executable_path = get_binary_from_path(
|
||||||
|
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("Failed to find required executable 'kubectl' and 'kustomize' in paths")
|
raise AnsibleLookupError(
|
||||||
|
"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("unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(executable_path))
|
raise AnsibleLookupError(
|
||||||
|
"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("kustomize command failed with: {0}".format(err.decode("utf-8")))
|
raise AnsibleLookupError(
|
||||||
return [out.decode('utf-8')]
|
"kustomize command failed with: {0}".format(err.decode("utf-8"))
|
||||||
|
)
|
||||||
|
return [out.decode("utf-8")]
|
||||||
|
|||||||
344
plugins/module_utils/_version.py
Normal file
344
plugins/module_utils/_version.py
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
# Vendored copy of distutils/version.py from CPython 3.9.5
|
||||||
|
#
|
||||||
|
# Implements multiple version numbering conventions for the
|
||||||
|
# Python Module Distribution Utilities.
|
||||||
|
#
|
||||||
|
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Provides classes to represent module version numbers (one class for
|
||||||
|
each style of version numbering). There are currently two such classes
|
||||||
|
implemented: StrictVersion and LooseVersion.
|
||||||
|
|
||||||
|
Every version number class implements the following interface:
|
||||||
|
* the 'parse' method takes a string and parses it to some internal
|
||||||
|
representation; if the string is an invalid version number,
|
||||||
|
'parse' raises a ValueError exception
|
||||||
|
* the class constructor takes an optional string argument which,
|
||||||
|
if supplied, is passed to 'parse'
|
||||||
|
* __str__ reconstructs the string that was passed to 'parse' (or
|
||||||
|
an equivalent string -- ie. one that will generate an equivalent
|
||||||
|
version number instance)
|
||||||
|
* __repr__ generates Python code to recreate the version number instance
|
||||||
|
* _cmp compares the current instance with either another instance
|
||||||
|
of the same class or a string (which will be parsed to an instance
|
||||||
|
of the same class, thus must follow the same rules)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
RE_FLAGS = re.VERBOSE | re.ASCII
|
||||||
|
except AttributeError:
|
||||||
|
RE_FLAGS = re.VERBOSE
|
||||||
|
|
||||||
|
|
||||||
|
class Version:
|
||||||
|
"""Abstract base class for version numbering classes. Just provides
|
||||||
|
constructor (__init__) and reproducer (__repr__), because those
|
||||||
|
seem to be the same for all version numbering classes; and route
|
||||||
|
rich comparisons to _cmp.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vstring=None):
|
||||||
|
if vstring:
|
||||||
|
self.parse(vstring)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s ('%s')" % (self.__class__.__name__, str(self))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c == 0
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c < 0
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c <= 0
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c > 0
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c >= 0
|
||||||
|
|
||||||
|
|
||||||
|
# Interface for version-number classes -- must be implemented
|
||||||
|
# by the following classes (the concrete ones -- Version should
|
||||||
|
# be treated as an abstract class).
|
||||||
|
# __init__ (string) - create and take same action as 'parse'
|
||||||
|
# (string parameter is optional)
|
||||||
|
# parse (string) - convert a string representation to whatever
|
||||||
|
# internal representation is appropriate for
|
||||||
|
# this style of version numbering
|
||||||
|
# __str__ (self) - convert back to a string; should be very similar
|
||||||
|
# (if not identical to) the string supplied to parse
|
||||||
|
# __repr__ (self) - generate Python code to recreate
|
||||||
|
# the instance
|
||||||
|
# _cmp (self, other) - compare two version numbers ('other' may
|
||||||
|
# be an unparsed version string, or another
|
||||||
|
# instance of your version class)
|
||||||
|
|
||||||
|
|
||||||
|
class StrictVersion(Version):
|
||||||
|
"""Version numbering for anal retentives and software idealists.
|
||||||
|
Implements the standard interface for version number classes as
|
||||||
|
described above. A version number consists of two or three
|
||||||
|
dot-separated numeric components, with an optional "pre-release" tag
|
||||||
|
on the end. The pre-release tag consists of the letter 'a' or 'b'
|
||||||
|
followed by a number. If the numeric components of two version
|
||||||
|
numbers are equal, then one with a pre-release tag will always
|
||||||
|
be deemed earlier (lesser) than one without.
|
||||||
|
|
||||||
|
The following are valid version numbers (shown in the order that
|
||||||
|
would be obtained by sorting according to the supplied cmp function):
|
||||||
|
|
||||||
|
0.4 0.4.0 (these two are equivalent)
|
||||||
|
0.4.1
|
||||||
|
0.5a1
|
||||||
|
0.5b3
|
||||||
|
0.5
|
||||||
|
0.9.6
|
||||||
|
1.0
|
||||||
|
1.0.4a3
|
||||||
|
1.0.4b1
|
||||||
|
1.0.4
|
||||||
|
|
||||||
|
The following are examples of invalid version numbers:
|
||||||
|
|
||||||
|
1
|
||||||
|
2.7.2.2
|
||||||
|
1.3.a4
|
||||||
|
1.3pl1
|
||||||
|
1.3c4
|
||||||
|
|
||||||
|
The rationale for this version numbering system will be explained
|
||||||
|
in the distutils documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
version_re = re.compile(r"^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$", RE_FLAGS)
|
||||||
|
|
||||||
|
def parse(self, vstring):
|
||||||
|
match = self.version_re.match(vstring)
|
||||||
|
if not match:
|
||||||
|
raise ValueError("invalid version number '%s'" % vstring)
|
||||||
|
|
||||||
|
(major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
|
||||||
|
|
||||||
|
if patch:
|
||||||
|
self.version = tuple(map(int, [major, minor, patch]))
|
||||||
|
else:
|
||||||
|
self.version = tuple(map(int, [major, minor])) + (0,)
|
||||||
|
|
||||||
|
if prerelease:
|
||||||
|
self.prerelease = (prerelease[0], int(prerelease_num))
|
||||||
|
else:
|
||||||
|
self.prerelease = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.version[2] == 0:
|
||||||
|
vstring = ".".join(map(str, self.version[0:2]))
|
||||||
|
else:
|
||||||
|
vstring = ".".join(map(str, self.version))
|
||||||
|
|
||||||
|
if self.prerelease:
|
||||||
|
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
|
||||||
|
|
||||||
|
return vstring
|
||||||
|
|
||||||
|
def _cmp(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
other = StrictVersion(other)
|
||||||
|
elif not isinstance(other, StrictVersion):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
if self.version != other.version:
|
||||||
|
# numeric versions don't match
|
||||||
|
# prerelease stuff doesn't matter
|
||||||
|
if self.version < other.version:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# have to compare prerelease
|
||||||
|
# case 1: neither has prerelease; they're equal
|
||||||
|
# case 2: self has prerelease, other doesn't; other is greater
|
||||||
|
# case 3: self doesn't have prerelease, other does: self is greater
|
||||||
|
# case 4: both have prerelease: must compare them!
|
||||||
|
|
||||||
|
if not self.prerelease and not other.prerelease:
|
||||||
|
return 0
|
||||||
|
elif self.prerelease and not other.prerelease:
|
||||||
|
return -1
|
||||||
|
elif not self.prerelease and other.prerelease:
|
||||||
|
return 1
|
||||||
|
elif self.prerelease and other.prerelease:
|
||||||
|
if self.prerelease == other.prerelease:
|
||||||
|
return 0
|
||||||
|
elif self.prerelease < other.prerelease:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
raise AssertionError("never get here")
|
||||||
|
|
||||||
|
|
||||||
|
# end class StrictVersion
|
||||||
|
|
||||||
|
# The rules according to Greg Stein:
|
||||||
|
# 1) a version number has 1 or more numbers separated by a period or by
|
||||||
|
# sequences of letters. If only periods, then these are compared
|
||||||
|
# left-to-right to determine an ordering.
|
||||||
|
# 2) sequences of letters are part of the tuple for comparison and are
|
||||||
|
# compared lexicographically
|
||||||
|
# 3) recognize the numeric components may have leading zeroes
|
||||||
|
#
|
||||||
|
# The LooseVersion class below implements these rules: a version number
|
||||||
|
# string is split up into a tuple of integer and string components, and
|
||||||
|
# comparison is a simple tuple comparison. This means that version
|
||||||
|
# numbers behave in a predictable and obvious way, but a way that might
|
||||||
|
# not necessarily be how people *want* version numbers to behave. There
|
||||||
|
# wouldn't be a problem if people could stick to purely numeric version
|
||||||
|
# numbers: just split on period and compare the numbers as tuples.
|
||||||
|
# However, people insist on putting letters into their version numbers;
|
||||||
|
# the most common purpose seems to be:
|
||||||
|
# - indicating a "pre-release" version
|
||||||
|
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
|
||||||
|
# - indicating a post-release patch ('p', 'pl', 'patch')
|
||||||
|
# but of course this can't cover all version number schemes, and there's
|
||||||
|
# no way to know what a programmer means without asking him.
|
||||||
|
#
|
||||||
|
# The problem is what to do with letters (and other non-numeric
|
||||||
|
# characters) in a version number. The current implementation does the
|
||||||
|
# obvious and predictable thing: keep them as strings and compare
|
||||||
|
# lexically within a tuple comparison. This has the desired effect if
|
||||||
|
# an appended letter sequence implies something "post-release":
|
||||||
|
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
|
||||||
|
#
|
||||||
|
# However, if letters in a version number imply a pre-release version,
|
||||||
|
# the "obvious" thing isn't correct. Eg. you would expect that
|
||||||
|
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
|
||||||
|
# implemented here, this just isn't so.
|
||||||
|
#
|
||||||
|
# Two possible solutions come to mind. The first is to tie the
|
||||||
|
# comparison algorithm to a particular set of semantic rules, as has
|
||||||
|
# been done in the StrictVersion class above. This works great as long
|
||||||
|
# as everyone can go along with bondage and discipline. Hopefully a
|
||||||
|
# (large) subset of Python module programmers will agree that the
|
||||||
|
# particular flavour of bondage and discipline provided by StrictVersion
|
||||||
|
# provides enough benefit to be worth using, and will submit their
|
||||||
|
# version numbering scheme to its domination. The free-thinking
|
||||||
|
# anarchists in the lot will never give in, though, and something needs
|
||||||
|
# to be done to accommodate them.
|
||||||
|
#
|
||||||
|
# Perhaps a "moderately strict" version class could be implemented that
|
||||||
|
# lets almost anything slide (syntactically), and makes some heuristic
|
||||||
|
# assumptions about non-digits in version number strings. This could
|
||||||
|
# sink into special-case-hell, though; if I was as talented and
|
||||||
|
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
|
||||||
|
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
|
||||||
|
# just as happy dealing with things like "2g6" and "1.13++". I don't
|
||||||
|
# think I'm smart enough to do it right though.
|
||||||
|
#
|
||||||
|
# In any case, I've coded the test suite for this module (see
|
||||||
|
# ../test/test_version.py) specifically to fail on things like comparing
|
||||||
|
# "1.2a2" and "1.2". That's not because the *code* is doing anything
|
||||||
|
# wrong, it's because the simple, obvious design doesn't match my
|
||||||
|
# complicated, hairy expectations for real-world version numbers. It
|
||||||
|
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
|
||||||
|
# the Right Thing" (ie. the code matches the conception). But I'd rather
|
||||||
|
# have a conception that matches common notions about version numbers.
|
||||||
|
|
||||||
|
|
||||||
|
class LooseVersion(Version):
|
||||||
|
"""Version numbering for anarchists and software realists.
|
||||||
|
Implements the standard interface for version number classes as
|
||||||
|
described above. A version number consists of a series of numbers,
|
||||||
|
separated by either periods or strings of letters. When comparing
|
||||||
|
version numbers, the numeric components will be compared
|
||||||
|
numerically, and the alphabetic components lexically. The following
|
||||||
|
are all valid version numbers, in no particular order:
|
||||||
|
|
||||||
|
1.5.1
|
||||||
|
1.5.2b2
|
||||||
|
161
|
||||||
|
3.10a
|
||||||
|
8.02
|
||||||
|
3.4j
|
||||||
|
1996.07.12
|
||||||
|
3.2.pl0
|
||||||
|
3.1.1.6
|
||||||
|
2g6
|
||||||
|
11g
|
||||||
|
0.960923
|
||||||
|
2.2beta29
|
||||||
|
1.13++
|
||||||
|
5.5.kw
|
||||||
|
2.0b1pl0
|
||||||
|
|
||||||
|
In fact, there is no such thing as an invalid version number under
|
||||||
|
this scheme; the rules for comparison are simple and predictable,
|
||||||
|
but may not always give the results you want (for some definition
|
||||||
|
of "want").
|
||||||
|
"""
|
||||||
|
|
||||||
|
component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
|
||||||
|
|
||||||
|
def __init__(self, vstring=None):
|
||||||
|
if vstring:
|
||||||
|
self.parse(vstring)
|
||||||
|
|
||||||
|
def parse(self, vstring):
|
||||||
|
# I've given up on thinking I can reconstruct the version string
|
||||||
|
# from the parsed tuple -- so I just store the string here for
|
||||||
|
# use by __str__
|
||||||
|
self.vstring = vstring
|
||||||
|
components = [x for x in self.component_re.split(vstring) if x and x != "."]
|
||||||
|
for i, obj in enumerate(components):
|
||||||
|
try:
|
||||||
|
components[i] = int(obj)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.version = components
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.vstring
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "LooseVersion ('%s')" % str(self)
|
||||||
|
|
||||||
|
def _cmp(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
other = LooseVersion(other)
|
||||||
|
elif not isinstance(other, LooseVersion):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
if self.version == other.version:
|
||||||
|
return 0
|
||||||
|
if self.version < other.version:
|
||||||
|
return -1
|
||||||
|
if self.version > other.version:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# end class LooseVersion
|
||||||
@@ -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,6 +17,7 @@ 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,13 +14,16 @@
|
|||||||
|
|
||||||
|
|
||||||
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 ApplyException
|
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
||||||
|
ApplyException,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.dynamic.exceptions import NotFoundError
|
from kubernetes.dynamic.exceptions import NotFoundError
|
||||||
@@ -28,50 +31,52 @@ 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()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -79,21 +84,28 @@ def annotate(desired):
|
|||||||
return dict(
|
return dict(
|
||||||
metadata=dict(
|
metadata=dict(
|
||||||
annotations={
|
annotations={
|
||||||
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True)
|
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(
|
||||||
|
desired, separators=(",", ":"), indent=None, sort_keys=True
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_patch(actual, desired):
|
def apply_patch(actual, desired):
|
||||||
last_applied = actual['metadata'].get('annotations', {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
|
last_applied = (
|
||||||
|
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(dict_merge(last_applied, annotate(last_applied)),
|
patch = merge(
|
||||||
dict_merge(desired, annotate(desired)), actual)
|
dict_merge(last_applied, annotate(last_applied)),
|
||||||
|
dict_merge(desired, annotate(desired)),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
if patch:
|
if patch:
|
||||||
return actual, patch
|
return actual, patch
|
||||||
else:
|
else:
|
||||||
@@ -102,24 +114,48 @@ def apply_patch(actual, desired):
|
|||||||
return actual, dict_merge(desired, annotate(desired))
|
return actual, dict_merge(desired, annotate(desired))
|
||||||
|
|
||||||
|
|
||||||
def apply_object(resource, definition):
|
def apply_object(resource, definition, server_side=False):
|
||||||
try:
|
try:
|
||||||
actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
actual = resource.get(
|
||||||
|
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):
|
def k8s_apply(resource, definition, **kwargs):
|
||||||
existing, desired = apply_object(resource, definition)
|
existing, desired = apply_object(resource, definition)
|
||||||
|
server_side = kwargs.get("server_side", False)
|
||||||
|
if server_side:
|
||||||
|
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"),
|
||||||
|
)
|
||||||
if not existing:
|
if not existing:
|
||||||
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
|
return resource.create(
|
||||||
|
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
|
||||||
|
)
|
||||||
if existing == desired:
|
if existing == desired:
|
||||||
return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
|
return resource.get(
|
||||||
return resource.patch(body=desired,
|
name=definition["metadata"]["name"],
|
||||||
name=definition['metadata']['name'],
|
namespace=definition["metadata"].get("namespace"),
|
||||||
namespace=definition['metadata'].get('namespace'),
|
)
|
||||||
content_type='application/merge-patch+json')
|
return resource.patch(
|
||||||
|
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
|
||||||
@@ -128,7 +164,7 @@ def k8s_apply(resource, definition):
|
|||||||
# 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)
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +174,9 @@ def list_to_dict(lst, key, position):
|
|||||||
try:
|
try:
|
||||||
result[item[key]] = item
|
result[item[key]] = item
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ApplyException("Expected key '%s' not found in position %s" % (key, position))
|
raise ApplyException(
|
||||||
|
"Expected key '%s' not found in position %s" % (key, position)
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +195,12 @@ 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(last_applied_dict[key], desired_dict[key], actual_dict[key], position)
|
patch = merge(
|
||||||
|
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:
|
||||||
@@ -197,11 +240,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):
|
||||||
@@ -246,11 +289,15 @@ 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(last_applied.get(k, {}), actual_value, desired_value, this_position)
|
p = get_delta(
|
||||||
|
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(last_applied.get(k, []), actual_value, desired_value, this_position)
|
p = list_merge(
|
||||||
|
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,133 +12,87 @@ 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': {
|
"kubeconfig": {"type": "raw"},
|
||||||
'type': 'raw',
|
"context": {},
|
||||||
},
|
"host": {},
|
||||||
'context': {},
|
"api_key": {"no_log": True},
|
||||||
'host': {},
|
"username": {},
|
||||||
'api_key': {
|
"password": {"no_log": True},
|
||||||
'no_log': True,
|
"validate_certs": {"type": "bool", "aliases": ["verify_ssl"]},
|
||||||
},
|
"ca_cert": {"type": "path", "aliases": ["ssl_ca_cert"]},
|
||||||
'username': {},
|
"client_cert": {"type": "path", "aliases": ["cert_file"]},
|
||||||
'password': {
|
"client_key": {"type": "path", "aliases": ["key_file"]},
|
||||||
'no_log': True,
|
"proxy": {"type": "str"},
|
||||||
},
|
"no_proxy": {"type": "str"},
|
||||||
'validate_certs': {
|
"proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC},
|
||||||
'type': 'bool',
|
"persist_config": {"type": "bool"},
|
||||||
'aliases': ['verify_ssl'],
|
"impersonate_user": {},
|
||||||
},
|
"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",
|
||||||
'proxy_headers': 'proxy_headers',
|
"no_proxy": "no_proxy",
|
||||||
'persist_config': 'persist_config',
|
"proxy_headers": "proxy_headers",
|
||||||
|
"persist_config": "persist_config",
|
||||||
}
|
}
|
||||||
|
|
||||||
NAME_ARG_SPEC = {
|
NAME_ARG_SPEC = {
|
||||||
'kind': {},
|
"kind": {},
|
||||||
'name': {},
|
"name": {},
|
||||||
'namespace': {},
|
"namespace": {},
|
||||||
'api_version': {
|
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
||||||
'default': 'v1',
|
|
||||||
'aliases': ['api', 'version'],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMON_ARG_SPEC = {
|
COMMON_ARG_SPEC = {
|
||||||
'state': {
|
"state": {"default": "present", "choices": ["present", "absent"]},
|
||||||
'default': 'present',
|
"force": {"type": "bool", "default": False},
|
||||||
'choices': ['present', 'absent'],
|
|
||||||
},
|
|
||||||
'force': {
|
|
||||||
'type': 'bool',
|
|
||||||
'default': False,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RESOURCE_ARG_SPEC = {
|
RESOURCE_ARG_SPEC = {
|
||||||
'resource_definition': {
|
"resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]},
|
||||||
'type': list_dict_str,
|
"src": {"type": "path"},
|
||||||
'aliases': ['definition', 'inline']
|
|
||||||
},
|
|
||||||
'src': {
|
|
||||||
'type': 'path',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
|
ARG_ATTRIBUTES_BLACKLIST = ("property_path",)
|
||||||
|
|
||||||
DELETE_OPTS_ARG_SPEC = {
|
DELETE_OPTS_ARG_SPEC = {
|
||||||
'propagationPolicy': {
|
"propagationPolicy": {"choices": ["Foreground", "Background", "Orphan"]},
|
||||||
'choices': ['Foreground', 'Background', 'Orphan'],
|
"gracePeriodSeconds": {"type": "int"},
|
||||||
|
"preconditions": {
|
||||||
|
"type": "dict",
|
||||||
|
"options": {"resourceVersion": {"type": "str"}, "uid": {"type": "str"}},
|
||||||
},
|
},
|
||||||
'gracePeriodSeconds': {
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'preconditions': {
|
|
||||||
'type': 'dict',
|
|
||||||
'options': {
|
|
||||||
'resourceVersion': {
|
|
||||||
'type': 'str',
|
|
||||||
},
|
|
||||||
'uid': {
|
|
||||||
'type': 'str',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,17 +23,26 @@ 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 (ResourceNotFoundError, ResourceNotUniqueError,
|
from kubernetes.dynamic.exceptions import (
|
||||||
ServiceUnavailableError)
|
ResourceNotFoundError,
|
||||||
|
ResourceNotUniqueError,
|
||||||
|
ServiceUnavailableError,
|
||||||
|
)
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
||||||
|
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(hashlib.sha256(self.__get_default_cache_id()).hexdigest())
|
default_cache_file_name = "k8srcp-{0}.json".format(
|
||||||
self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cache_file_name)
|
hashlib.sha256(self.__get_default_cache_id()).hexdigest()
|
||||||
|
)
|
||||||
|
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):
|
||||||
@@ -42,21 +51,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:
|
||||||
@@ -70,13 +79,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:
|
||||||
@@ -92,21 +101,25 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
|||||||
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(filter(lambda resource: '/' not in resource['name'], resources_response))
|
resources_raw = list(
|
||||||
subresources_raw = list(filter(lambda resource: '/' 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)
|
||||||
|
)
|
||||||
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(
|
||||||
@@ -115,19 +128,25 @@ 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(self.client, group=group, api_version=version, base_kind=resource['kind'], base_resource_lookup=resource_lookup)
|
resource_list = ResourceList(
|
||||||
|
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
|
||||||
|
|
||||||
@@ -139,23 +158,32 @@ 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 for result in results if result.group_version == kwargs['api_version']
|
result
|
||||||
|
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 = [result for result in results if not isinstance(result, ResourceList)]
|
results = [
|
||||||
|
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 len(results) > 1 and len(set((x.group_version, x.kind) for x in results)) == 1:
|
if (
|
||||||
|
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('Multiple matches found for {0}: {1}'.format(kwargs, results))
|
raise ResourceNotUniqueError(
|
||||||
|
"Multiple matches found for {0}: {1}".format(kwargs, results)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
|
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
|
||||||
@@ -174,13 +202,15 @@ 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(obj['preferred'], resources=self.object_hook(obj['resources']))
|
return kubernetes.dynamic.discovery.ResourceGroup(
|
||||||
|
obj["preferred"], resources=self.object_hook(obj["resources"])
|
||||||
|
)
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
@@ -21,11 +22,19 @@ import kubernetes.dynamic
|
|||||||
|
|
||||||
|
|
||||||
class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
class ResourceList(kubernetes.dynamic.resource.ResourceList):
|
||||||
def __init__(self, client, group='', api_version='v1', base_kind='', kind=None, base_resource_lookup=None):
|
def __init__(
|
||||||
|
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
|
||||||
@@ -34,16 +43,18 @@ 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_lookup)
|
self.__base_resource = self.client.resources.get(
|
||||||
|
**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
409
plugins/module_utils/copy.py
Normal file
409
plugins/module_utils/copy.py
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
# 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.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")
|
||||||
|
|
||||||
|
@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):
|
||||||
|
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 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):
|
||||||
|
# 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
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
# 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
|
||||||
@@ -23,6 +24,7 @@ import hashlib
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import string
|
import string
|
||||||
|
|
||||||
maketrans = string.maketrans
|
maketrans = string.maketrans
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
maketrans = str.maketrans
|
maketrans = str.maketrans
|
||||||
@@ -44,14 +46,21 @@ def sorted_dict(unsorted_dict):
|
|||||||
|
|
||||||
def generate_hash(resource):
|
def generate_hash(resource):
|
||||||
# Get name from metadata
|
# Get name from metadata
|
||||||
resource['name'] = resource.get('metadata', {}).get('name', '')
|
metada = resource.get("metadata", {})
|
||||||
if resource['kind'] == 'ConfigMap':
|
key = "name"
|
||||||
marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name'])
|
resource["name"] = metada.get("name", "")
|
||||||
del(resource['name'])
|
generate_name = metada.get("generateName", "")
|
||||||
|
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', 'name', 'type'])
|
marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"])
|
||||||
del(resource['name'])
|
del resource[key]
|
||||||
return encode(marshalled)
|
return encode(marshalled)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -60,8 +69,10 @@ 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 hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
return (
|
||||||
|
hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ from contextlib import contextmanager
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
import re
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
HAS_YAML = True
|
HAS_YAML = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
YAML_IMP_ERR = traceback.format_exc()
|
YAML_IMP_ERR = traceback.format_exc()
|
||||||
@@ -27,11 +29,11 @@ except ImportError:
|
|||||||
def prepare_helm_environ_update(module):
|
def prepare_helm_environ_update(module):
|
||||||
environ_update = {}
|
environ_update = {}
|
||||||
file_to_cleam_up = None
|
file_to_cleam_up = None
|
||||||
kubeconfig_path = module.params.get('kubeconfig')
|
kubeconfig_path = module.params.get("kubeconfig")
|
||||||
if module.params.get('context') is not None:
|
if module.params.get("context") is not None:
|
||||||
environ_update["HELM_KUBECONTEXT"] = module.params.get('context')
|
environ_update["HELM_KUBECONTEXT"] = module.params.get("context")
|
||||||
if module.params.get('release_namespace'):
|
if module.params.get("release_namespace"):
|
||||||
environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace')
|
environ_update["HELM_NAMESPACE"] = module.params.get("release_namespace")
|
||||||
if module.params.get("api_key"):
|
if module.params.get("api_key"):
|
||||||
environ_update["HELM_KUBETOKEN"] = module.params["api_key"]
|
environ_update["HELM_KUBETOKEN"] = module.params["api_key"]
|
||||||
if module.params.get("host"):
|
if module.params.get("host"):
|
||||||
@@ -40,7 +42,8 @@ def prepare_helm_environ_update(module):
|
|||||||
kubeconfig_path = write_temp_kubeconfig(
|
kubeconfig_path = write_temp_kubeconfig(
|
||||||
module.params["host"],
|
module.params["host"],
|
||||||
validate_certs=module.params["validate_certs"],
|
validate_certs=module.params["validate_certs"],
|
||||||
ca_cert=module.params["ca_cert"])
|
ca_cert=module.params["ca_cert"],
|
||||||
|
)
|
||||||
file_to_cleam_up = kubeconfig_path
|
file_to_cleam_up = kubeconfig_path
|
||||||
if kubeconfig_path is not None:
|
if kubeconfig_path is not None:
|
||||||
environ_update["KUBECONFIG"] = kubeconfig_path
|
environ_update["KUBECONFIG"] = kubeconfig_path
|
||||||
@@ -60,7 +63,9 @@ def run_helm(module, command, fails_on_error=True):
|
|||||||
rc, out, err = module.run_command(command, environ_update=environ_update)
|
rc, out, err = module.run_command(command, environ_update=environ_update)
|
||||||
if fails_on_error and rc != 0:
|
if fails_on_error and rc != 0:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||||
|
rc, out, err
|
||||||
|
),
|
||||||
stdout=out,
|
stdout=out,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
command=command,
|
command=command,
|
||||||
@@ -89,23 +94,11 @@ def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
|
|||||||
content = {
|
content = {
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Config",
|
"kind": "Config",
|
||||||
"clusters": [
|
"clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}],
|
||||||
{
|
|
||||||
"cluster": {
|
|
||||||
"server": server,
|
|
||||||
},
|
|
||||||
"name": "generated-cluster"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"contexts": [
|
"contexts": [
|
||||||
{
|
{"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
|
||||||
"context": {
|
|
||||||
"cluster": "generated-cluster"
|
|
||||||
},
|
|
||||||
"name": "generated-context"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"current-context": "generated-context"
|
"current-context": "generated-context",
|
||||||
}
|
}
|
||||||
|
|
||||||
if not validate_certs:
|
if not validate_certs:
|
||||||
@@ -114,7 +107,7 @@ def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
|
|||||||
content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert
|
content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert
|
||||||
|
|
||||||
_fd, file_name = tempfile.mkstemp()
|
_fd, file_name = tempfile.mkstemp()
|
||||||
with os.fdopen(_fd, 'w') as fp:
|
with os.fdopen(_fd, "w") as fp:
|
||||||
yaml.dump(content, fp)
|
yaml.dump(content, fp)
|
||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
@@ -127,7 +120,7 @@ def get_helm_plugin_list(module, helm_bin=None):
|
|||||||
return []
|
return []
|
||||||
helm_plugin_list = helm_bin + " list"
|
helm_plugin_list = helm_bin + " list"
|
||||||
rc, out, err = run_helm(module, helm_plugin_list)
|
rc, out, err = run_helm(module, helm_plugin_list)
|
||||||
if rc != 0 or (out == '' and err == ''):
|
if rc != 0 or (out == "" and err == ""):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Failed to get Helm plugin info",
|
msg="Failed to get Helm plugin info",
|
||||||
command=helm_plugin_list,
|
command=helm_plugin_list,
|
||||||
@@ -149,12 +142,23 @@ def parse_helm_plugin_list(module, 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 get_helm_version(module, helm_bin):
|
||||||
|
|
||||||
|
helm_version_command = helm_bin + " version"
|
||||||
|
rc, out, err = module.run_command(helm_version_command)
|
||||||
|
if rc == 0:
|
||||||
|
m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
return None
|
||||||
|
|||||||
@@ -14,26 +14,37 @@
|
|||||||
|
|
||||||
|
|
||||||
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 ApplyException
|
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
|
||||||
|
ApplyException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class K8SDynamicClient(DynamicClient):
|
class K8SDynamicClient(DynamicClient):
|
||||||
def apply(self, resource, body=None, name=None, namespace=None):
|
def apply(self, resource, body=None, name=None, namespace=None, **kwargs):
|
||||||
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("name is required to apply {0}.{1}".format(resource.group_version, resource.kind))
|
raise ValueError(
|
||||||
|
"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(resource, namespace, body)
|
body["metadata"]["namespace"] = super().ensure_namespace(
|
||||||
|
resource, namespace, body
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
return k8s_apply(resource, body)
|
return k8s_apply(resource, body, **kwargs)
|
||||||
except ApplyException as e:
|
except ApplyException as e:
|
||||||
raise ValueError("Could not apply strategic merge to %s/%s: %s" %
|
raise ValueError(
|
||||||
(body['kind'], body['metadata']['name'], e))
|
"Could not apply strategic merge to %s/%s: %s"
|
||||||
|
% (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,18 +27,23 @@ 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(r'( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)', data)
|
m = re.match(
|
||||||
|
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(" ")
|
||||||
@@ -54,18 +59,21 @@ 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 self._key not in labels if self._operator == "!" else self._key in labels
|
return (
|
||||||
|
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)
|
||||||
|
|||||||
18
plugins/module_utils/version.py
Normal file
18
plugins/module_utils/version.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
"""Provide version object to compare version numbers."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
|
||||||
|
# remove the _version.py file, and replace the following import by
|
||||||
|
#
|
||||||
|
# from ansible.module_utils.compat.version import LooseVersion
|
||||||
|
|
||||||
|
from ._version import LooseVersion # noqa: F401
|
||||||
@@ -4,10 +4,11 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ 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:
|
||||||
@@ -86,7 +91,7 @@ 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.
|
- Run C(helm repo update) before the operation. Can be run as part of the package installation or as a separate step (see Examples).
|
||||||
default: false
|
default: false
|
||||||
type: bool
|
type: bool
|
||||||
|
|
||||||
@@ -108,13 +113,24 @@ options:
|
|||||||
type: bool
|
type: bool
|
||||||
wait:
|
wait:
|
||||||
description:
|
description:
|
||||||
- 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.
|
- When I(release_state) is set to C(present), 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.
|
||||||
|
- 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.
|
||||||
@@ -148,9 +164,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
|
||||||
@@ -193,6 +209,13 @@ 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
|
||||||
|
|
||||||
# From git
|
# From git
|
||||||
- name: Git clone stable repo on HEAD
|
- name: Git clone stable repo on HEAD
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
@@ -237,7 +260,7 @@ EXAMPLES = r'''
|
|||||||
enabled: True
|
enabled: True
|
||||||
logging:
|
logging:
|
||||||
enabled: True
|
enabled: True
|
||||||
'''
|
"""
|
||||||
|
|
||||||
RETURN = r"""
|
RETURN = r"""
|
||||||
status:
|
status:
|
||||||
@@ -296,9 +319,13 @@ command:
|
|||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
||||||
|
LooseVersion,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
@@ -309,7 +336,8 @@ from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
|||||||
run_helm,
|
run_helm,
|
||||||
get_values,
|
get_values,
|
||||||
get_helm_plugin_list,
|
get_helm_plugin_list,
|
||||||
parse_helm_plugin_list
|
parse_helm_plugin_list,
|
||||||
|
get_helm_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -320,7 +348,7 @@ 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
|
||||||
|
|
||||||
@@ -339,7 +367,7 @@ def get_release_status(module, command, release_name):
|
|||||||
if release is None: # not install
|
if release is None: # not install
|
||||||
return None
|
return None
|
||||||
|
|
||||||
release['values'] = get_values(module, command, release_name)
|
release["values"] = get_values(module, command, release_name)
|
||||||
|
|
||||||
return release
|
return release
|
||||||
|
|
||||||
@@ -363,9 +391,23 @@ def fetch_chart_info(module, command, chart_ref):
|
|||||||
return yaml.safe_load(out)
|
return yaml.safe_load(out)
|
||||||
|
|
||||||
|
|
||||||
def deploy(command, release_name, release_values, chart_name, wait,
|
def deploy(
|
||||||
wait_timeout, disable_hook, force, values_files, history_max, atomic=False,
|
command,
|
||||||
create_namespace=False, replace=False, skip_crds=False):
|
release_name,
|
||||||
|
release_values,
|
||||||
|
chart_name,
|
||||||
|
wait,
|
||||||
|
wait_timeout,
|
||||||
|
disable_hook,
|
||||||
|
force,
|
||||||
|
values_files,
|
||||||
|
history_max,
|
||||||
|
atomic=False,
|
||||||
|
create_namespace=False,
|
||||||
|
replace=False,
|
||||||
|
skip_crds=False,
|
||||||
|
timeout=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Install/upgrade/rollback release chart
|
Install/upgrade/rollback release chart
|
||||||
"""
|
"""
|
||||||
@@ -386,6 +428,9 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
|||||||
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"
|
||||||
|
|
||||||
@@ -403,8 +448,8 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
|||||||
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
|
||||||
|
|
||||||
@@ -418,7 +463,7 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
|||||||
return deploy_command
|
return deploy_command
|
||||||
|
|
||||||
|
|
||||||
def delete(command, release_name, purge, disable_hook):
|
def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
|
||||||
"""
|
"""
|
||||||
Delete release chart
|
Delete release chart
|
||||||
"""
|
"""
|
||||||
@@ -431,6 +476,12 @@ def delete(command, release_name, purge, disable_hook):
|
|||||||
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
|
||||||
@@ -439,7 +490,7 @@ def delete(command, release_name, purge, disable_hook):
|
|||||||
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
|
||||||
@@ -448,9 +499,9 @@ def load_values_files(values_files):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def has_plugin(command, plugin):
|
def get_plugin_version(command, plugin):
|
||||||
"""
|
"""
|
||||||
Check if helm plugin is installed.
|
Check if helm plugin is installed and return corresponding version
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cmd = command + " plugin"
|
cmd = command + " plugin"
|
||||||
@@ -458,16 +509,25 @@ def has_plugin(command, plugin):
|
|||||||
out = parse_helm_plugin_list(module, output=output.splitlines())
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
if not out:
|
if not out:
|
||||||
return False
|
return None
|
||||||
|
|
||||||
for line in out:
|
for line in out:
|
||||||
if line[0] == plugin:
|
if line[0] == plugin:
|
||||||
return True
|
return line[1]
|
||||||
return False
|
return None
|
||||||
|
|
||||||
|
|
||||||
def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
def helmdiff_check(
|
||||||
values_files=None, chart_version=None, replace=False):
|
module,
|
||||||
|
helm_cmd,
|
||||||
|
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.
|
||||||
"""
|
"""
|
||||||
@@ -475,14 +535,16 @@ def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
|||||||
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
|
||||||
|
|
||||||
@@ -491,7 +553,7 @@ def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
|
|||||||
cmd += " -f=" + values_file
|
cmd += " -f=" + values_file
|
||||||
|
|
||||||
rc, out, err = run_helm(module, cmd)
|
rc, out, err = run_helm(module, cmd)
|
||||||
return len(out.strip()) > 0
|
return (len(out.strip()) > 0, out.strip())
|
||||||
|
|
||||||
|
|
||||||
def default_check(release_status, chart_info, values=None, values_files=None):
|
def default_check(release_status, chart_info, values=None, values_files=None):
|
||||||
@@ -499,64 +561,89 @@ 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 (chart_app_version is None and released_app_version == "")
|
appversion_is_same = (chart_app_version == released_app_version) or (
|
||||||
|
chart_app_version is None and released_app_version == ""
|
||||||
|
)
|
||||||
|
|
||||||
if values_files:
|
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 not values_match \
|
return (
|
||||||
or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"] \
|
not values_match
|
||||||
|
or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
|
||||||
or not appversion_is_same
|
or not appversion_is_same
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global module
|
global module
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type='path'),
|
binary_path=dict(type="path"),
|
||||||
chart_ref=dict(type='path'),
|
chart_ref=dict(type="path"),
|
||||||
chart_repo_url=dict(type='str'),
|
chart_repo_url=dict(type="str"),
|
||||||
chart_version=dict(type='str'),
|
chart_version=dict(type="str"),
|
||||||
release_name=dict(type='str', required=True, aliases=['name']),
|
release_name=dict(type="str", required=True, aliases=["name"]),
|
||||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
|
||||||
release_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
|
release_state=dict(
|
||||||
release_values=dict(type='dict', default={}, aliases=['values']),
|
default="present", choices=["present", "absent"], aliases=["state"]
|
||||||
values_files=dict(type='list', default=[], elements='str'),
|
),
|
||||||
update_repo_cache=dict(type='bool', default=False),
|
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
|
# Helm options
|
||||||
disable_hook=dict(type='bool', default=False),
|
disable_hook=dict(type="bool", default=False),
|
||||||
force=dict(type='bool', default=False),
|
force=dict(type="bool", default=False),
|
||||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
context=dict(
|
||||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
type="str",
|
||||||
purge=dict(type='bool', default=True),
|
aliases=["kube_context"],
|
||||||
wait=dict(type='bool', default=False),
|
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
|
||||||
wait_timeout=dict(type='str'),
|
),
|
||||||
atomic=dict(type='bool', default=False),
|
kubeconfig=dict(
|
||||||
create_namespace=dict(type='bool', default=False),
|
type="path",
|
||||||
replace=dict(type='bool', default=False),
|
aliases=["kubeconfig_path"],
|
||||||
skip_crds=dict(type='bool', default=False),
|
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
|
||||||
history_max=dict(type='int'),
|
),
|
||||||
|
purge=dict(type="bool", default=True),
|
||||||
|
wait=dict(type="bool", default=False),
|
||||||
|
wait_timeout=dict(type="str"),
|
||||||
|
timeout=dict(type="str"),
|
||||||
|
atomic=dict(type="bool", default=False),
|
||||||
|
create_namespace=dict(type="bool", default=False),
|
||||||
|
replace=dict(type="bool", default=False),
|
||||||
|
skip_crds=dict(type="bool", default=False),
|
||||||
|
history_max=dict(type="int"),
|
||||||
# Generic auth key
|
# Generic auth key
|
||||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
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'])),
|
ca_cert=dict(
|
||||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
type="path",
|
||||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
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"),
|
("kubeconfig", "ca_cert"),
|
||||||
("replace", "history_max"),
|
("replace", "history_max"),
|
||||||
|
("wait_timeout", "timeout"),
|
||||||
],
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
@@ -566,32 +653,33 @@ def main():
|
|||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
chart_ref = module.params.get('chart_ref')
|
chart_ref = module.params.get("chart_ref")
|
||||||
chart_repo_url = module.params.get('chart_repo_url')
|
chart_repo_url = module.params.get("chart_repo_url")
|
||||||
chart_version = module.params.get('chart_version')
|
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")
|
||||||
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:
|
if bin_path is not None:
|
||||||
helm_cmd_common = bin_path
|
helm_cmd_common = bin_path
|
||||||
else:
|
else:
|
||||||
helm_cmd_common = module.get_bin_path('helm', required=True)
|
helm_cmd_common = module.get_bin_path("helm", required=True)
|
||||||
|
|
||||||
if update_repo_cache:
|
if update_repo_cache:
|
||||||
run_repo_update(module, helm_cmd_common)
|
run_repo_update(module, helm_cmd_common)
|
||||||
@@ -601,11 +689,23 @@ def main():
|
|||||||
|
|
||||||
# keep helm_cmd_common for get_release_status in module_exit_json
|
# keep helm_cmd_common for get_release_status in module_exit_json
|
||||||
helm_cmd = helm_cmd_common
|
helm_cmd = helm_cmd_common
|
||||||
|
opt_result = {}
|
||||||
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")
|
||||||
|
|
||||||
helm_cmd = delete(helm_cmd, release_name, purge, disable_hook)
|
if wait:
|
||||||
|
helm_version = get_helm_version(module, helm_cmd_common)
|
||||||
|
if LooseVersion(helm_version) < LooseVersion("3.7.0"):
|
||||||
|
opt_result["warnings"] = []
|
||||||
|
opt_result["warnings"].append(
|
||||||
|
"helm uninstall support option --wait for helm release >= 3.7.0"
|
||||||
|
)
|
||||||
|
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":
|
||||||
|
|
||||||
@@ -619,54 +719,99 @@ def main():
|
|||||||
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
|
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
|
||||||
|
|
||||||
if release_status is None: # Not installed
|
if release_status is None: # Not installed
|
||||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
helm_cmd = deploy(
|
||||||
disable_hook, False, values_files=values_files, atomic=atomic,
|
helm_cmd,
|
||||||
create_namespace=create_namespace, replace=replace,
|
release_name,
|
||||||
skip_crds=skip_crds, history_max=history_max)
|
release_values,
|
||||||
|
chart_ref,
|
||||||
|
wait,
|
||||||
|
wait_timeout,
|
||||||
|
disable_hook,
|
||||||
|
False,
|
||||||
|
values_files=values_files,
|
||||||
|
atomic=atomic,
|
||||||
|
create_namespace=create_namespace,
|
||||||
|
replace=replace,
|
||||||
|
skip_crds=skip_crds,
|
||||||
|
history_max=history_max,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if has_plugin(helm_cmd_common, "diff") and not chart_repo_url:
|
helm_diff_version = get_plugin_version(helm_cmd_common, "diff")
|
||||||
would_change = helmdiff_check(module, helm_cmd_common, release_name, chart_ref,
|
if helm_diff_version and (
|
||||||
release_values, values_files, chart_version, replace)
|
not chart_repo_url
|
||||||
|
or (
|
||||||
|
chart_repo_url
|
||||||
|
and LooseVersion(helm_diff_version) >= LooseVersion("3.4.1")
|
||||||
|
)
|
||||||
|
):
|
||||||
|
(would_change, prepared) = helmdiff_check(
|
||||||
|
module,
|
||||||
|
helm_cmd_common,
|
||||||
|
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("The default idempotency check can fail to report changes in certain cases. "
|
module.warn(
|
||||||
"Install helm diff for better results.")
|
"The default idempotency check can fail to report changes in certain cases. "
|
||||||
would_change = default_check(release_status, chart_info, release_values, values_files)
|
"Install helm diff >= 3.4.1 for better results."
|
||||||
|
)
|
||||||
|
would_change = default_check(
|
||||||
|
release_status, chart_info, release_values, values_files
|
||||||
|
)
|
||||||
|
|
||||||
if force or would_change:
|
if force or would_change:
|
||||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
helm_cmd = deploy(
|
||||||
disable_hook, force, values_files=values_files, atomic=atomic,
|
helm_cmd,
|
||||||
create_namespace=create_namespace, replace=replace,
|
release_name,
|
||||||
skip_crds=skip_crds, history_max=history_max)
|
release_values,
|
||||||
|
chart_ref,
|
||||||
|
wait,
|
||||||
|
wait_timeout,
|
||||||
|
disable_hook,
|
||||||
|
force,
|
||||||
|
values_files=values_files,
|
||||||
|
atomic=atomic,
|
||||||
|
create_namespace=create_namespace,
|
||||||
|
replace=replace,
|
||||||
|
skip_crds=skip_crds,
|
||||||
|
history_max=history_max,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
check_status = {
|
check_status = {"values": {"current": {}, "declared": {}}}
|
||||||
'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 = run_helm(module, helm_cmd)
|
rc, out, err = run_helm(module, helm_cmd)
|
||||||
@@ -677,8 +822,9 @@ def main():
|
|||||||
stderr=err,
|
stderr=err,
|
||||||
status=get_release_status(module, helm_cmd_common, 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,10 +4,11 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -38,18 +39,39 @@ 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"
|
||||||
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 Grafana chart inside monitoring namespace
|
- name: Gather information of Grafana chart inside monitoring namespace
|
||||||
kubernetes.core.helm_info:
|
kubernetes.core.helm_info:
|
||||||
name: test
|
name: test
|
||||||
release_namespace: monitoring
|
release_namespace: monitoring
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
- name: Gather information about test-chart with pending state
|
||||||
|
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
|
||||||
@@ -87,40 +109,61 @@ status:
|
|||||||
type: str
|
type: str
|
||||||
returned: always
|
returned: always
|
||||||
description: Dict of Values used to deploy
|
description: Dict of Values used to deploy
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
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 AnsibleModule, missing_required_lib, env_fallback
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm, get_values
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
||||||
|
run_helm,
|
||||||
|
get_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Get Release from all deployed releases
|
# 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, command, release_name):
|
def get_release_status(module, command, release_name, release_state):
|
||||||
list_command = command + " list --output=yaml --filter " + release_name
|
list_command = command + " list --output=yaml"
|
||||||
|
|
||||||
|
valid_release_states = [
|
||||||
|
"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 = run_helm(module, list_command)
|
rc, out, err = run_helm(module, 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(rc, out, err),
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||||
command=list_command
|
rc, out, err
|
||||||
|
),
|
||||||
|
command=list_command,
|
||||||
)
|
)
|
||||||
|
|
||||||
release = get_release(yaml.safe_load(out), release_name)
|
release = get_release(yaml.safe_load(out), release_name)
|
||||||
@@ -128,7 +171,7 @@ def get_release_status(module, command, release_name):
|
|||||||
if release is None: # not install
|
if release is None: # not install
|
||||||
return None
|
return None
|
||||||
|
|
||||||
release['values'] = get_values(module, command, release_name)
|
release["values"] = get_values(module, command, release_name)
|
||||||
|
|
||||||
return release
|
return release
|
||||||
|
|
||||||
@@ -138,25 +181,43 @@ def main():
|
|||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type='path'),
|
binary_path=dict(type="path"),
|
||||||
release_name=dict(type='str', required=True, aliases=['name']),
|
release_name=dict(type="str", required=True, aliases=["name"]),
|
||||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
|
||||||
|
|
||||||
# Helm options
|
# Helm options
|
||||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
context=dict(
|
||||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
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
|
# Generic auth key
|
||||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
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'])),
|
ca_cert=dict(
|
||||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
type="path",
|
||||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
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"])
|
||||||
|
),
|
||||||
|
release_state=dict(type="list", default=[], elements="str"),
|
||||||
),
|
),
|
||||||
mutually_exclusive=[
|
mutually_exclusive=[
|
||||||
("context", "ca_cert"),
|
("context", "ca_cert"),
|
||||||
("context", "validate_certs"),
|
("context", "validate_certs"),
|
||||||
("kubeconfig", "ca_cert"),
|
("kubeconfig", "ca_cert"),
|
||||||
("kubeconfig", "validate_certs")
|
("kubeconfig", "validate_certs"),
|
||||||
],
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
@@ -164,15 +225,18 @@ def main():
|
|||||||
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)
|
||||||
|
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
release_name = module.params.get('release_name')
|
release_name = module.params.get("release_name")
|
||||||
|
release_state = module.params.get("release_state")
|
||||||
|
|
||||||
if bin_path is not None:
|
if bin_path is not None:
|
||||||
helm_cmd_common = bin_path
|
helm_cmd_common = bin_path
|
||||||
else:
|
else:
|
||||||
helm_cmd_common = module.get_bin_path('helm', required=True)
|
helm_cmd_common = module.get_bin_path("helm", required=True)
|
||||||
|
|
||||||
release_status = get_release_status(module, helm_cmd_common, release_name)
|
release_status = get_release_status(
|
||||||
|
module, helm_cmd_common, release_name, release_state
|
||||||
|
)
|
||||||
|
|
||||||
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)
|
||||||
@@ -180,5 +244,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,14 +24,15 @@ 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 ]
|
choices: [ absent, present, latest ]
|
||||||
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).
|
- Required only if C(state=absent) or C(state=latest).
|
||||||
type: str
|
type: str
|
||||||
plugin_path:
|
plugin_path:
|
||||||
description:
|
description:
|
||||||
@@ -40,11 +41,18 @@ 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
|
||||||
@@ -59,9 +67,20 @@ EXAMPLES = r'''
|
|||||||
kubernetes.core.helm_plugin:
|
kubernetes.core.helm_plugin:
|
||||||
plugin_name: env
|
plugin_name: env
|
||||||
state: absent
|
state: absent
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
- name: Install Helm plugin with a specific version
|
||||||
|
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
|
||||||
@@ -87,67 +106,98 @@ rc:
|
|||||||
description: Helm plugin command return code
|
description: Helm plugin command return code
|
||||||
returned: always
|
returned: always
|
||||||
sample: 1
|
sample: 1
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
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 (
|
||||||
run_helm,
|
run_helm,
|
||||||
get_helm_plugin_list,
|
get_helm_plugin_list,
|
||||||
parse_helm_plugin_list
|
parse_helm_plugin_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type='path'),
|
binary_path=dict(type="path"),
|
||||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
state=dict(
|
||||||
plugin_path=dict(type='str',),
|
type="str", default="present", choices=["present", "absent", "latest"]
|
||||||
plugin_name=dict(type='str',),
|
),
|
||||||
|
plugin_path=dict(
|
||||||
|
type="str",
|
||||||
|
),
|
||||||
|
plugin_name=dict(
|
||||||
|
type="str",
|
||||||
|
),
|
||||||
|
plugin_version=dict(
|
||||||
|
type="str",
|
||||||
|
),
|
||||||
# Helm options
|
# Helm options
|
||||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
context=dict(
|
||||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
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
|
# Generic auth key
|
||||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
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'])),
|
ca_cert=dict(
|
||||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
type="path",
|
||||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
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=[
|
||||||
('plugin_name', 'plugin_path'),
|
("plugin_name", "plugin_path"),
|
||||||
("context", "ca_cert"),
|
("context", "ca_cert"),
|
||||||
("context", "validate_certs"),
|
("context", "validate_certs"),
|
||||||
("kubeconfig", "ca_cert"),
|
("kubeconfig", "ca_cert"),
|
||||||
("kubeconfig", "validate_certs")
|
("kubeconfig", "validate_certs"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
state = module.params.get('state')
|
state = module.params.get("state")
|
||||||
|
|
||||||
if bin_path is not None:
|
if bin_path is not None:
|
||||||
helm_cmd_common = bin_path
|
helm_cmd_common = bin_path
|
||||||
else:
|
else:
|
||||||
helm_cmd_common = 'helm'
|
helm_cmd_common = "helm"
|
||||||
|
|
||||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||||
|
|
||||||
helm_cmd_common += " plugin"
|
helm_cmd_common += " plugin"
|
||||||
|
|
||||||
if state == 'present':
|
if state == "present":
|
||||||
helm_cmd_common += " install %s" % module.params.get('plugin_path')
|
helm_cmd_common += " install %s" % module.params.get("plugin_path")
|
||||||
|
plugin_version = module.params.get("plugin_version")
|
||||||
|
if plugin_version is not None:
|
||||||
|
helm_cmd_common += " --version=%s" % plugin_version
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
rc, out, err = run_helm(module, helm_cmd_common, fails_on_error=False)
|
rc, out, err = run_helm(module, 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,
|
||||||
@@ -155,7 +205,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(
|
||||||
@@ -175,8 +225,8 @@ 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 = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||||
out = parse_helm_plugin_list(module, output=output.splitlines())
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
@@ -188,7 +238,7 @@ def main():
|
|||||||
command=helm_cmd_common + " list",
|
command=helm_cmd_common + " list",
|
||||||
stdout=output,
|
stdout=output,
|
||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc
|
rc=rc,
|
||||||
)
|
)
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
@@ -204,14 +254,14 @@ def main():
|
|||||||
command=helm_cmd_common + " list",
|
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 = run_helm(module, helm_uninstall_cmd, fails_on_error=False)
|
rc, out, err = run_helm(module, 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(
|
||||||
@@ -220,7 +270,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",
|
||||||
@@ -229,7 +279,61 @@ def main():
|
|||||||
stderr=err,
|
stderr=err,
|
||||||
rc=rc,
|
rc=rc,
|
||||||
)
|
)
|
||||||
|
elif state == "latest":
|
||||||
|
plugin_name = module.params.get("plugin_name")
|
||||||
|
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
|
||||||
|
out = parse_helm_plugin_list(module, output=output.splitlines())
|
||||||
|
|
||||||
|
if not out:
|
||||||
|
module.exit_json(
|
||||||
|
failed=False,
|
||||||
|
changed=False,
|
||||||
|
msg="Plugin not found",
|
||||||
|
command=helm_cmd_common + " list",
|
||||||
|
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=helm_cmd_common + " list",
|
||||||
|
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 = run_helm(module, 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,7 +68,7 @@ rc:
|
|||||||
description: Helm plugin command return code
|
description: Helm plugin command return code
|
||||||
returned: always
|
returned: always
|
||||||
sample: 1
|
sample: 1
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
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 (
|
||||||
@@ -80,39 +80,59 @@ from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type='path'),
|
binary_path=dict(type="path"),
|
||||||
plugin_name=dict(type='str',),
|
plugin_name=dict(
|
||||||
|
type="str",
|
||||||
|
),
|
||||||
# Helm options
|
# Helm options
|
||||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
context=dict(
|
||||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
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
|
# Generic auth key
|
||||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
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'])),
|
ca_cert=dict(
|
||||||
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
|
type="path",
|
||||||
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
|
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=[
|
mutually_exclusive=[
|
||||||
("context", "ca_cert"),
|
("context", "ca_cert"),
|
||||||
("context", "validate_certs"),
|
("context", "validate_certs"),
|
||||||
("kubeconfig", "ca_cert"),
|
("kubeconfig", "ca_cert"),
|
||||||
("kubeconfig", "validate_certs")
|
("kubeconfig", "validate_certs"),
|
||||||
],
|
],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
|
|
||||||
if bin_path is not None:
|
if bin_path is not None:
|
||||||
helm_cmd_common = bin_path
|
helm_cmd_common = bin_path
|
||||||
else:
|
else:
|
||||||
helm_cmd_common = 'helm'
|
helm_cmd_common = "helm"
|
||||||
|
|
||||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||||
|
|
||||||
helm_cmd_common += " plugin"
|
helm_cmd_common += " plugin"
|
||||||
|
|
||||||
plugin_name = module.params.get('plugin_name')
|
plugin_name = module.params.get("plugin_name")
|
||||||
|
|
||||||
plugin_list = []
|
plugin_list = []
|
||||||
|
|
||||||
@@ -123,21 +143,13 @@ def main():
|
|||||||
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
|
||||||
|
|
||||||
@@ -151,5 +163,5 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -64,9 +65,41 @@ 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"
|
||||||
|
"""
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r"""
|
||||||
- name: Add a repository
|
- name: Add a repository
|
||||||
kubernetes.core.helm_repository:
|
kubernetes.core.helm_repository:
|
||||||
name: stable
|
name: stable
|
||||||
@@ -76,9 +109,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
|
||||||
@@ -109,18 +142,19 @@ 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
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
IMP_YAML = True
|
IMP_YAML = True
|
||||||
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 AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
||||||
|
|
||||||
|
|
||||||
@@ -128,7 +162,7 @@ from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_he
|
|||||||
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
|
||||||
|
|
||||||
@@ -144,21 +178,33 @@ def get_repository_status(module, command, repository_name):
|
|||||||
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(rc, out, err),
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||||
command=list_command
|
rc, out, err
|
||||||
|
),
|
||||||
|
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(command, repository_name, repository_url, repository_username, repository_password):
|
def install_repository(
|
||||||
|
command,
|
||||||
|
repository_name,
|
||||||
|
repository_url,
|
||||||
|
repository_username,
|
||||||
|
repository_password,
|
||||||
|
pass_credentials,
|
||||||
|
):
|
||||||
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"
|
||||||
|
|
||||||
return install_command
|
return install_command
|
||||||
|
|
||||||
|
|
||||||
@@ -174,19 +220,34 @@ def main():
|
|||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
binary_path=dict(type='path'),
|
binary_path=dict(type="path"),
|
||||||
repo_name=dict(type='str', aliases=['name'], required=True),
|
repo_name=dict(type="str", aliases=["name"], required=True),
|
||||||
repo_url=dict(type='str', aliases=['url']),
|
repo_url=dict(type="str", aliases=["url"]),
|
||||||
repo_username=dict(type='str', aliases=['username']),
|
repo_username=dict(type="str", aliases=["username"]),
|
||||||
repo_password=dict(type='str', aliases=['password'], no_log=True),
|
repo_password=dict(type="str", aliases=["password"], no_log=True),
|
||||||
repo_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
|
repo_state=dict(
|
||||||
|
default="present", choices=["present", "absent"], aliases=["state"]
|
||||||
),
|
),
|
||||||
required_together=[
|
pass_credentials=dict(type="bool", default=False, no_log=True),
|
||||||
['repo_username', 'repo_password']
|
# Generic auth key
|
||||||
],
|
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
|
||||||
required_if=[
|
ca_cert=dict(
|
||||||
('repo_state', 'present', ['repo_url']),
|
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_together=[["repo_username", "repo_password"]],
|
||||||
|
required_if=[("repo_state", "present", ["repo_url"])],
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,17 +256,18 @@ def main():
|
|||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
repo_name = module.params.get('repo_name')
|
repo_name = module.params.get("repo_name")
|
||||||
repo_url = module.params.get('repo_url')
|
repo_url = module.params.get("repo_url")
|
||||||
repo_username = module.params.get('repo_username')
|
repo_username = module.params.get("repo_username")
|
||||||
repo_password = module.params.get('repo_password')
|
repo_password = module.params.get("repo_password")
|
||||||
repo_state = module.params.get('repo_state')
|
repo_state = module.params.get("repo_state")
|
||||||
|
pass_credentials = module.params.get("pass_credentials")
|
||||||
|
|
||||||
if bin_path is not None:
|
if bin_path is not None:
|
||||||
helm_cmd = bin_path
|
helm_cmd = bin_path
|
||||||
else:
|
else:
|
||||||
helm_cmd = module.get_bin_path('helm', required=True)
|
helm_cmd = module.get_bin_path("helm", required=True)
|
||||||
|
|
||||||
repository_status = get_repository_status(module, helm_cmd, repo_name)
|
repository_status = get_repository_status(module, helm_cmd, repo_name)
|
||||||
|
|
||||||
@@ -214,10 +276,19 @@ def main():
|
|||||||
changed = True
|
changed = True
|
||||||
elif repo_state == "present":
|
elif repo_state == "present":
|
||||||
if repository_status is None:
|
if repository_status is None:
|
||||||
helm_cmd = install_repository(helm_cmd, repo_name, repo_url, repo_username, repo_password)
|
helm_cmd = install_repository(
|
||||||
|
helm_cmd,
|
||||||
|
repo_name,
|
||||||
|
repo_url,
|
||||||
|
repo_username,
|
||||||
|
repo_password,
|
||||||
|
pass_credentials,
|
||||||
|
)
|
||||||
changed = True
|
changed = True
|
||||||
elif repository_status['url'] != repo_url:
|
elif repository_status["url"] != repo_url:
|
||||||
module.fail_json(msg="Repository already have a repository named {0}".format(repo_name))
|
module.fail_json(
|
||||||
|
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)
|
||||||
@@ -227,16 +298,18 @@ def main():
|
|||||||
rc, out, err = run_helm(module, 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(rc, out, err),
|
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
|
||||||
command=helm_cmd
|
rc, out, err
|
||||||
|
),
|
||||||
|
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
|
||||||
|
|
||||||
@@ -57,6 +57,12 @@ 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_namespace:
|
||||||
|
description:
|
||||||
|
- namespace scope for this request.
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
|
version_added: 2.3.0
|
||||||
release_values:
|
release_values:
|
||||||
description:
|
description:
|
||||||
- Values to pass to chart.
|
- Values to pass to chart.
|
||||||
@@ -64,6 +70,13 @@ 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.3.0
|
||||||
values_files:
|
values_files:
|
||||||
description:
|
description:
|
||||||
- Value files to pass to chart.
|
- Value files to pass to chart.
|
||||||
@@ -79,9 +92,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
|
||||||
'''
|
"""
|
||||||
|
|
||||||
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
|
||||||
@@ -96,9 +109,27 @@ EXAMPLES = r'''
|
|||||||
copy:
|
copy:
|
||||||
dest: myfile.yaml
|
dest: myfile.yaml
|
||||||
content: "{{ result.stdout }}"
|
content: "{{ result.stdout }}"
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
- name: Render MutatingWebhooksConfiguration for revision tag "canary", rev "1-13-0"
|
||||||
|
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.
|
||||||
@@ -114,13 +145,14 @@ command:
|
|||||||
description: Full C(helm) command run by this module, in case you want to re-run the command outside the module or debug a problem.
|
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
|
||||||
except ImportError:
|
except ImportError:
|
||||||
IMP_YAML_ERR = traceback.format_exc()
|
IMP_YAML_ERR = traceback.format_exc()
|
||||||
@@ -130,8 +162,18 @@ from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
|||||||
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
|
||||||
|
|
||||||
|
|
||||||
def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir=None,
|
def template(
|
||||||
release_values=None, values_files=None, include_crds=False):
|
cmd,
|
||||||
|
chart_ref,
|
||||||
|
chart_repo_url=None,
|
||||||
|
chart_version=None,
|
||||||
|
output_dir=None,
|
||||||
|
show_only=None,
|
||||||
|
release_values=None,
|
||||||
|
release_namespace=None,
|
||||||
|
values_files=None,
|
||||||
|
include_crds=False,
|
||||||
|
):
|
||||||
cmd += " template " + chart_ref
|
cmd += " template " + chart_ref
|
||||||
|
|
||||||
if chart_repo_url:
|
if chart_repo_url:
|
||||||
@@ -143,16 +185,23 @@ def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir
|
|||||||
if output_dir:
|
if output_dir:
|
||||||
cmd += " --output-dir=" + output_dir
|
cmd += " --output-dir=" + output_dir
|
||||||
|
|
||||||
if release_values:
|
if show_only:
|
||||||
fd, path = tempfile.mkstemp(suffix='.yml')
|
for template in show_only:
|
||||||
with open(path, 'w') as yaml_file:
|
cmd += " -s " + template
|
||||||
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"
|
||||||
|
|
||||||
@@ -162,43 +211,55 @@ def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
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"),
|
||||||
include_crds=dict(type='bool', default=False),
|
include_crds=dict(type="bool", default=False),
|
||||||
output_dir=dict(type='path'),
|
output_dir=dict(type="path"),
|
||||||
release_values=dict(type='dict', default={}, aliases=['values']),
|
release_namespace=dict(type="str"),
|
||||||
values_files=dict(type='list', default=[], elements='str'),
|
release_values=dict(type="dict", default={}, aliases=["values"]),
|
||||||
update_repo_cache=dict(type='bool', default=False)
|
show_only=dict(type="list", default=[], elements="str"),
|
||||||
|
values_files=dict(type="list", default=[], elements="str"),
|
||||||
|
update_repo_cache=dict(type="bool", default=False),
|
||||||
),
|
),
|
||||||
supports_check_mode=True
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
check_mode = module.check_mode
|
check_mode = module.check_mode
|
||||||
bin_path = module.params.get('binary_path')
|
bin_path = module.params.get("binary_path")
|
||||||
chart_ref = module.params.get('chart_ref')
|
chart_ref = module.params.get("chart_ref")
|
||||||
chart_repo_url = module.params.get('chart_repo_url')
|
chart_repo_url = module.params.get("chart_repo_url")
|
||||||
chart_version = module.params.get('chart_version')
|
chart_version = module.params.get("chart_version")
|
||||||
include_crds = module.params.get('include_crds')
|
include_crds = module.params.get("include_crds")
|
||||||
output_dir = module.params.get('output_dir')
|
output_dir = module.params.get("output_dir")
|
||||||
release_values = module.params.get('release_values')
|
show_only = module.params.get("show_only")
|
||||||
values_files = module.params.get('values_files')
|
release_namespace = module.params.get("release_namespace")
|
||||||
update_repo_cache = module.params.get('update_repo_cache')
|
release_values = module.params.get("release_values")
|
||||||
|
values_files = module.params.get("values_files")
|
||||||
|
update_repo_cache = module.params.get("update_repo_cache")
|
||||||
|
|
||||||
if not IMP_YAML:
|
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 = bin_path or module.get_bin_path('helm', required=True)
|
helm_cmd = bin_path or module.get_bin_path("helm", required=True)
|
||||||
|
|
||||||
if update_repo_cache:
|
if update_repo_cache:
|
||||||
update_cmd = helm_cmd + " repo update"
|
update_cmd = helm_cmd + " repo update"
|
||||||
run_helm(module, update_cmd)
|
run_helm(module, update_cmd)
|
||||||
|
|
||||||
tmpl_cmd = template(helm_cmd, chart_ref, chart_repo_url=chart_repo_url,
|
tmpl_cmd = template(
|
||||||
chart_version=chart_version, output_dir=output_dir,
|
helm_cmd,
|
||||||
release_values=release_values, values_files=values_files,
|
chart_ref,
|
||||||
include_crds=include_crds)
|
chart_repo_url=chart_repo_url,
|
||||||
|
chart_version=chart_version,
|
||||||
|
output_dir=output_dir,
|
||||||
|
release_namespace=release_namespace,
|
||||||
|
release_values=release_values,
|
||||||
|
show_only=show_only,
|
||||||
|
values_files=values_files,
|
||||||
|
include_crds=include_crds,
|
||||||
|
)
|
||||||
|
|
||||||
if not check_mode:
|
if not check_mode:
|
||||||
rc, out, err = run_helm(module, tmpl_cmd)
|
rc, out, err = run_helm(module, tmpl_cmd)
|
||||||
@@ -207,14 +268,9 @@ def main():
|
|||||||
rc = 0
|
rc = 0
|
||||||
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
failed=False,
|
failed=False, changed=True, command=tmpl_cmd, stdout=out, stderr=err, rc=rc
|
||||||
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,15 +142,45 @@ 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
|
||||||
@@ -278,9 +308,36 @@ EXAMPLES = r'''
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
support: patch
|
support: patch
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
# Create object using generateName
|
||||||
|
- 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.
|
||||||
@@ -320,20 +377,34 @@ 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 AnsibleModule
|
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, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
|
AUTH_ARG_SPEC,
|
||||||
|
WAIT_ARG_SPEC,
|
||||||
|
NAME_ARG_SPEC,
|
||||||
|
RESOURCE_ARG_SPEC,
|
||||||
|
DELETE_OPTS_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_spec():
|
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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -342,16 +413,26 @@ 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(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
|
argument_spec["merge_type"] = dict(
|
||||||
argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec())
|
type="list", elements="str", choices=["json", "merge", "strategic-merge"]
|
||||||
argument_spec['append_hash'] = dict(type='bool', default=False)
|
)
|
||||||
argument_spec['apply'] = dict(type='bool', default=False)
|
argument_spec["validate"] = dict(type="dict", default=None, options=validate_spec())
|
||||||
argument_spec['template'] = dict(type='raw', default=None)
|
argument_spec["append_hash"] = dict(type="bool", default=False)
|
||||||
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
|
argument_spec["apply"] = dict(type="bool", default=False)
|
||||||
argument_spec['continue_on_error'] = dict(type='bool', default=False)
|
argument_spec["template"] = dict(type="raw", default=None)
|
||||||
argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched'])
|
argument_spec["delete_options"] = dict(
|
||||||
argument_spec['force'] = dict(type='bool', default=False)
|
type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)
|
||||||
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
|
||||||
|
|
||||||
@@ -367,10 +448,11 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn
|
k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn
|
||||||
k8s_ansible_mixin.warnings = []
|
k8s_ansible_mixin.warnings = []
|
||||||
|
|
||||||
k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind')
|
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.api_version = k8s_ansible_mixin.params.get("api_version")
|
||||||
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get('name')
|
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get("name")
|
||||||
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
|
k8s_ansible_mixin.generate_name = k8s_ansible_mixin.params.get("generate_name")
|
||||||
|
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get("namespace")
|
||||||
|
|
||||||
k8s_ansible_mixin.check_library_version()
|
k8s_ansible_mixin.check_library_version()
|
||||||
k8s_ansible_mixin.set_resource_definitions(module)
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
@@ -379,19 +461,26 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
|
|
||||||
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)
|
module = AnsibleModule(
|
||||||
|
argument_spec=argspec(),
|
||||||
|
mutually_exclusive=mutually_exclusive,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
# 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"
|
||||||
@@ -36,9 +37,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
|
||||||
@@ -47,9 +48,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
|
||||||
@@ -136,7 +137,7 @@ apis:
|
|||||||
description: Resource singular name
|
description: Resource singular name
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
@@ -145,7 +146,10 @@ from collections import defaultdict
|
|||||||
|
|
||||||
HAS_K8S = False
|
HAS_K8S = False
|
||||||
try:
|
try:
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
|
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
|
||||||
|
ResourceList,
|
||||||
|
)
|
||||||
|
|
||||||
HAS_K8S = True
|
HAS_K8S = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
K8S_IMP_ERR = e
|
K8S_IMP_ERR = e
|
||||||
@@ -154,12 +158,18 @@ except ImportError as e:
|
|||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.parsing.convert_bool import boolean
|
from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
|
AnsibleModule,
|
||||||
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
|
AUTH_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, client):
|
def execute_module(module, client):
|
||||||
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
|
invalidate_cache = boolean(
|
||||||
|
module.params.get("invalidate_cache", True), strict=False
|
||||||
|
)
|
||||||
if invalidate_cache:
|
if invalidate_cache:
|
||||||
client.resources.invalidate_cache()
|
client.resources.invalidate_cache()
|
||||||
results = defaultdict(dict)
|
results = defaultdict(dict)
|
||||||
@@ -167,47 +177,60 @@ def execute_module(module, client):
|
|||||||
resource = resource[0]
|
resource = resource[0]
|
||||||
if isinstance(resource, ResourceList):
|
if isinstance(resource, ResourceList):
|
||||||
continue
|
continue
|
||||||
key = resource.group_version if resource.group == '' else '/'.join([resource.group, resource.group_version.split('/')[-1]])
|
key = (
|
||||||
|
resource.group_version
|
||||||
|
if resource.group == ""
|
||||||
|
else "/".join([resource.group, resource.group_version.split("/")[-1]])
|
||||||
|
)
|
||||||
results[key][resource.kind] = {
|
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.version,
|
"server": client.version,
|
||||||
}
|
}
|
||||||
module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
|
module.exit_json(
|
||||||
|
changed=False, apis=results, connection=connection, version=version_info
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
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 = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
if not HAS_K8S:
|
if not HAS_K8S:
|
||||||
module.fail_json(msg=missing_required_lib('kubernetes'), exception=K8S_IMP_EXC,
|
module.fail_json(
|
||||||
error=to_native(K8S_IMP_ERR))
|
msg=missing_required_lib("kubernetes"),
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import get_api_client
|
exception=K8S_IMP_EXC,
|
||||||
|
error=to_native(K8S_IMP_ERR),
|
||||||
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
execute_module(module, client=get_api_client(module=module))
|
execute_module(module, client=get_api_client(module=module))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -78,9 +79,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:
|
||||||
@@ -125,327 +126,57 @@ EXAMPLES = r'''
|
|||||||
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 AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
AnsibleModule,
|
||||||
|
)
|
||||||
|
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
|
|
||||||
|
|
||||||
|
|
||||||
class K8SCopy(metaclass=ABCMeta):
|
def argspec():
|
||||||
|
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
def __init__(self, module, client):
|
argument_spec["namespace"] = {"type": "str", "required": True}
|
||||||
self.client = client
|
argument_spec["pod"] = {"type": "str", "required": True}
|
||||||
self.module = module
|
argument_spec["container"] = {}
|
||||||
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
argument_spec["remote_path"] = {"type": "path", "required": True}
|
||||||
|
argument_spec["local_path"] = {"type": "path"}
|
||||||
self.local_path = module.params.get('local_path')
|
argument_spec["content"] = {"type": "str"}
|
||||||
self.name = module.params.get('pod')
|
argument_spec["state"] = {
|
||||||
self.namespace = module.params.get('namespace')
|
"type": "str",
|
||||||
self.remote_path = module.params.get('remote_path')
|
"default": "to_pod",
|
||||||
self.content = module.params.get('content')
|
"choices": ["to_pod", "from_pod"],
|
||||||
|
}
|
||||||
self.no_preserve = module.params.get('no_preserve')
|
argument_spec["no_preserve"] = {"type": "bool", "default": False}
|
||||||
self.container_arg = {}
|
return argument_spec
|
||||||
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):
|
||||||
|
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module, pyyaml_required=False)
|
k8s_ansible_mixin = K8sAnsibleMixin(module, pyyaml_required=False)
|
||||||
k8s_ansible_mixin.check_library_version()
|
k8s_ansible_mixin.check_library_version()
|
||||||
|
|
||||||
@@ -457,37 +188,34 @@ def execute_module(module):
|
|||||||
|
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
containers = check_pod(k8s_ansible_mixin, module)
|
containers = check_pod(k8s_ansible_mixin, module)
|
||||||
if len(containers) > 1 and module.params.get('container') is None:
|
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")
|
module.fail_json(
|
||||||
|
msg="Pod contains more than 1 container, option 'container' should be set"
|
||||||
|
)
|
||||||
|
|
||||||
|
state = module.params.get("state")
|
||||||
|
if state == "to_pod":
|
||||||
|
k8s_copy = K8SCopyToPod(module, k8s_ansible_mixin.client)
|
||||||
|
else:
|
||||||
|
k8s_copy = K8SCopyFromPod(module, k8s_ansible_mixin.client)
|
||||||
|
|
||||||
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():
|
||||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
module = AnsibleModule(
|
||||||
argument_spec['namespace'] = {'type': 'str', 'required': True}
|
argument_spec=argspec(),
|
||||||
argument_spec['pod'] = {'type': 'str', 'required': True}
|
mutually_exclusive=[("local_path", "content")],
|
||||||
argument_spec['container'] = {}
|
required_if=[("state", "from_pod", ["local_path"])],
|
||||||
argument_spec['remote_path'] = {'type': 'path', 'required': True}
|
required_one_of=[["local_path", "content"]],
|
||||||
argument_spec['local_path'] = {'type': 'path'}
|
supports_check_mode=True,
|
||||||
argument_spec['content'] = {'type': 'str'}
|
)
|
||||||
argument_spec['state'] = {'type': 'str', 'default': 'to_pod', 'choices': ['to_pod', 'from_pod']}
|
|
||||||
argument_spec['no_preserve'] = {'type': 'bool', 'default': False}
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=[('local_path', 'content')],
|
|
||||||
required_if=[('state', 'from_pod', ['local_path'])],
|
|
||||||
required_one_of=[['local_path', 'content']],
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
execute_module(module)
|
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,6 +64,12 @@ 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.
|
||||||
@@ -83,9 +89,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
|
||||||
@@ -109,32 +115,53 @@ 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
|
||||||
from datetime import datetime
|
|
||||||
import time
|
import time
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
import traceback
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC
|
|
||||||
|
from datetime import datetime
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
|
AnsibleModule,
|
||||||
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
|
AUTH_ARG_SPEC,
|
||||||
|
)
|
||||||
from ansible.module_utils._text import to_native
|
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 V1beta1Eviction, V1DeleteOptions
|
from kubernetes.client.models import V1DeleteOptions, V1ObjectMeta
|
||||||
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.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
HAS_EVICTION_API = True
|
||||||
|
k8s_import_exception = None
|
||||||
|
K8S_IMP_ERR = None
|
||||||
|
|
||||||
def filter_pods(pods, force, ignore_daemonset):
|
try:
|
||||||
|
from kubernetes.client.models import V1beta1Eviction as v1_eviction
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
from kubernetes.client.models import V1Eviction as v1_eviction
|
||||||
|
except ImportError as e:
|
||||||
|
k8s_import_exception = e
|
||||||
|
K8S_IMP_ERR = traceback.format_exc()
|
||||||
|
HAS_EVICTION_API = False
|
||||||
|
|
||||||
|
|
||||||
|
def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
||||||
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:
|
||||||
@@ -144,12 +171,11 @@ def filter_pods(pods, force, ignore_daemonset):
|
|||||||
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
|
||||||
@@ -167,39 +193,62 @@ def filter_pods(pods, force, ignore_daemonset):
|
|||||||
|
|
||||||
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("cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
errors.append(
|
||||||
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(pod_names))
|
"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
||||||
|
" 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("Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(pod_names))
|
warnings.append(
|
||||||
|
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
|
||||||
|
pod_names
|
||||||
|
)
|
||||||
|
)
|
||||||
to_delete += unmanaged
|
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("cannot delete mirror Pods using API server: {0}.".format(pod_names))
|
warnings.append(
|
||||||
|
"cannot delete mirror Pods using API server: {0}.".format(pod_names)
|
||||||
|
)
|
||||||
|
|
||||||
# local storage
|
# local storage
|
||||||
if localStorage:
|
if localStorage:
|
||||||
errors.append("cannot delete Pods with local storage: {0}.".format(pod_names))
|
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)
|
||||||
|
)
|
||||||
|
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("cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(pod_names))
|
errors.append(
|
||||||
|
"cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(
|
||||||
|
pod_names
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
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):
|
def __init__(self, module):
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
self._module = module
|
self._module = module
|
||||||
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
|
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
@@ -215,17 +264,18 @@ class K8sDrainAnsible(object):
|
|||||||
self._k8s_ansible_mixin.warn = self._module.warn
|
self._k8s_ansible_mixin.warn = self._module.warn
|
||||||
self._k8s_ansible_mixin.warnings = []
|
self._k8s_ansible_mixin.warnings = []
|
||||||
|
|
||||||
self._api_instance = core_v1_api.CoreV1Api(self._k8s_ansible_mixin.client.client)
|
self._api_instance = core_v1_api.CoreV1Api(
|
||||||
|
self._k8s_ansible_mixin.client.client
|
||||||
|
)
|
||||||
self._k8s_ansible_mixin.check_library_version()
|
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 = {}
|
self._delete_options = V1DeleteOptions(
|
||||||
self._delete_options.update({'apiVersion': 'v1'})
|
grace_period_seconds=self._drain_options.get("terminate_grace_period")
|
||||||
self._delete_options.update({'kind': 'DeleteOptions'})
|
)
|
||||||
self._delete_options.update({'gracePeriodSeconds': self._drain_options.get('terminate_grace_period')})
|
|
||||||
|
|
||||||
self._changed = False
|
self._changed = False
|
||||||
|
|
||||||
@@ -241,13 +291,17 @@ class K8sDrainAnsible(object):
|
|||||||
if not pod:
|
if not pod:
|
||||||
pod = pods.pop()
|
pod = pods.pop()
|
||||||
try:
|
try:
|
||||||
response = self._api_instance.read_namespaced_pod(namespace=pod[0], name=pod[1])
|
response = self._api_instance.read_namespaced_pod(
|
||||||
|
namespace=pod[0], name=pod[1]
|
||||||
|
)
|
||||||
if not response:
|
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(msg="Exception raised: {0}".format(exc.reason))
|
self._module.fail_json(
|
||||||
|
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)))
|
||||||
@@ -257,37 +311,49 @@ 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"):
|
||||||
body = V1DeleteOptions(**definition)
|
self._api_instance.delete_namespaced_pod(
|
||||||
self._api_instance.delete_namespaced_pod(name=name, namespace=namespace, body=body)
|
name=name, namespace=namespace, body=self._delete_options
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
body = V1beta1Eviction(**definition)
|
body = v1_eviction(
|
||||||
self._api_instance.create_namespaced_pod_eviction(name=name, namespace=namespace, body=body)
|
delete_options=self._delete_options,
|
||||||
|
metadata=V1ObjectMeta(name=name, namespace=namespace),
|
||||||
|
)
|
||||||
|
self._api_instance.create_namespaced_pod_eviction(
|
||||||
|
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(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, exc.reason))
|
self._module.fail_json(
|
||||||
|
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
||||||
|
namespace, name, exc.reason
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, to_native(exc)))
|
self._module.fail_json(
|
||||||
|
msg="Failed to delete pod {0}/{1} due to: {2}".format(
|
||||||
|
namespace, name, to_native(exc)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def delete_or_evict_pods(self, node_unschedulable):
|
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("node {0} marked unschedulable.".format(self._module.params.get('name')))
|
result.append(
|
||||||
|
"node {0} marked unschedulable.".format(self._module.params.get("name"))
|
||||||
|
)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
else:
|
else:
|
||||||
result.append("node {0} already marked unschedulable.".format(self._module.params.get('name')))
|
result.append(
|
||||||
|
"node {0} already marked unschedulable.".format(
|
||||||
|
self._module.params.get("name")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _revert_node_patch():
|
def _revert_node_patch():
|
||||||
if self._changed:
|
if self._changed:
|
||||||
@@ -295,77 +361,112 @@ class K8sDrainAnsible(object):
|
|||||||
self.patch_node(unschedulable=False)
|
self.patch_node(unschedulable=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
field_selector = "spec.nodeName={name}".format(name=self._module.params.get('name'))
|
field_selector = "spec.nodeName={name}".format(
|
||||||
pod_list = self._api_instance.list_pod_for_all_namespaces(field_selector=field_selector)
|
name=self._module.params.get("name")
|
||||||
|
)
|
||||||
|
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)
|
||||||
pods, warnings, errors = filter_pods(pod_list.items, force, ignore_daemonset)
|
delete_emptydir_data = self._drain_options.get(
|
||||||
|
"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(msg="Pod deletion errors: {0}".format(" ".join(errors)))
|
self._module.fail_json(
|
||||||
|
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(msg="Failed to list pod from node {name} due to: {reason}".format(
|
self._module.fail_json(
|
||||||
name=self._module.params.get('name'), reason=exc.reason), status=exc.status)
|
msg="Failed to list pod from node {name} due to: {reason}".format(
|
||||||
|
name=self._module.params.get("name"), reason=exc.reason
|
||||||
|
),
|
||||||
|
status=exc.status,
|
||||||
|
)
|
||||||
pods = []
|
pods = []
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_revert_node_patch()
|
_revert_node_patch()
|
||||||
self._module.fail_json(msg="Failed to list pod from node {name} due to: {error}".format(
|
self._module.fail_json(
|
||||||
name=self._module.params.get('name'), error=to_native(exc)))
|
msg="Failed to list pod from node {name} due to: {error}".format(
|
||||||
|
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(pods,
|
warn = self.wait_for_pod_deletion(
|
||||||
self._drain_options.get('wait_timeout'),
|
pods,
|
||||||
self._drain_options.get('wait_sleep'))
|
self._drain_options.get("wait_timeout"),
|
||||||
|
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 = {
|
body = {"spec": {"unschedulable": unschedulable}}
|
||||||
'spec': {'unschedulable': unschedulable}
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
self._api_instance.patch_node(name=self._module.params.get('name'), body=body)
|
self._api_instance.patch_node(
|
||||||
|
name=self._module.params.get("name"), body=body
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(msg="Failed to patch node due to: {0}".format(to_native(exc)))
|
self._module.fail_json(
|
||||||
|
msg="Failed to patch node due to: {0}".format(to_native(exc))
|
||||||
|
)
|
||||||
|
|
||||||
def execute_module(self):
|
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(msg="Failed to retrieve node '{0}' due to: {1}".format(name, exc.reason), status=exc.status)
|
self._module.fail_json(
|
||||||
|
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
||||||
|
name, exc.reason
|
||||||
|
),
|
||||||
|
status=exc.status,
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, to_native(exc)))
|
self._module.fail_json(
|
||||||
|
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
||||||
|
name, to_native(exc)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
if state == "cordon":
|
if state == "cordon":
|
||||||
if node.spec.unschedulable:
|
if node.spec.unschedulable:
|
||||||
self._module.exit_json(result="node {0} already marked unschedulable.".format(name))
|
self._module.exit_json(
|
||||||
|
result="node {0} already marked unschedulable.".format(name)
|
||||||
|
)
|
||||||
self.patch_node(unschedulable=True)
|
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(result="node {0} already marked schedulable.".format(name))
|
self._module.exit_json(
|
||||||
|
result="node {0} already marked schedulable.".format(name)
|
||||||
|
)
|
||||||
self.patch_node(unschedulable=False)
|
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:
|
||||||
@@ -384,16 +485,17 @@ 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),
|
||||||
disable_eviction=dict(type='bool', default=False),
|
delete_emptydir_data=dict(type="bool", default=False),
|
||||||
wait_timeout=dict(type='int'),
|
disable_eviction=dict(type="bool", default=False),
|
||||||
wait_sleep=dict(type='int', default=5),
|
wait_timeout=dict(type="int"),
|
||||||
)
|
wait_sleep=dict(type="int", default=5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -403,9 +505,16 @@ def argspec():
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(argument_spec=argspec())
|
module = AnsibleModule(argument_spec=argspec())
|
||||||
|
|
||||||
|
if not HAS_EVICTION_API:
|
||||||
|
module.fail_json(
|
||||||
|
msg="The kubernetes Python library missing with V1Eviction API",
|
||||||
|
exception=K8S_IMP_ERR,
|
||||||
|
error=to_native(k8s_import_exception),
|
||||||
|
)
|
||||||
|
|
||||||
k8s_drain = K8sDrainAnsible(module)
|
k8s_drain = K8sDrainAnsible(module)
|
||||||
k8s_drain.execute_module()
|
k8s_drain.execute_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_exec
|
module: k8s_exec
|
||||||
|
|
||||||
@@ -40,32 +40,33 @@ 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 (e.g. HTTP_PROXY).
|
- Please note that this module does not pick up typical proxy settings from the environment (for example, 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
|
||||||
@@ -84,9 +85,16 @@ EXAMPLES = r'''
|
|||||||
debug:
|
debug:
|
||||||
msg: "cmd failed"
|
msg: "cmd failed"
|
||||||
when: command_status.rc != 0
|
when: command_status.rc != 0
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
- name: Specify a container name to execute the command on
|
||||||
|
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
|
||||||
@@ -112,7 +120,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
|
||||||
@@ -123,15 +131,18 @@ 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 AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
@@ -139,10 +150,10 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -153,8 +164,21 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
|
|
||||||
# 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,
|
||||||
@@ -165,10 +189,14 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
stderr=True,
|
stderr=True,
|
||||||
stdin=False,
|
stdin=False,
|
||||||
tty=False,
|
tty=False,
|
||||||
_preload_content=False, **optional_kwargs)
|
_preload_content=False,
|
||||||
|
**optional_kwargs
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="Failed to execute on pod %s"
|
module.fail_json(
|
||||||
" due to : %s" % (module.params.get('pod'), to_native(e)))
|
msg="Failed to execute on pod %s"
|
||||||
|
" 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)
|
||||||
@@ -178,19 +206,23 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
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("The 'return_code' return key is deprecated. Please use 'rc' instead.", version="4.0.0", collection_name="kubernetes.core")
|
module.deprecate(
|
||||||
|
"The 'return_code' return key is deprecated. Please use 'rc' instead.",
|
||||||
|
version="4.0.0",
|
||||||
|
collection_name="kubernetes.core",
|
||||||
|
)
|
||||||
module.exit_json(
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -200,12 +232,14 @@ def main():
|
|||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
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,12 +144,17 @@ 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 AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
|
AnsibleModule,
|
||||||
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
|
AUTH_ARG_SPEC,
|
||||||
|
WAIT_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, k8s_ansible_mixin):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
@@ -174,11 +179,11 @@ 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
|
||||||
@@ -187,12 +192,18 @@ def argspec():
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
|
k8s_ansible_mixin.fail_json = module.fail_json
|
||||||
|
k8s_ansible_mixin.fail = module.fail_json
|
||||||
|
k8s_ansible_mixin.exit_json = module.exit_json
|
||||||
|
k8s_ansible_mixin.warn = module.warn
|
||||||
execute_module(module, k8s_ansible_mixin)
|
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
|
||||||
@@ -81,9 +81,9 @@ EXAMPLES = r'''
|
|||||||
- op: replace
|
- op: replace
|
||||||
patch: /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,17 +122,24 @@ 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 AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC
|
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.common import (
|
||||||
get_api_client, K8sAnsibleMixin)
|
get_api_client,
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from kubernetes.dynamic.exceptions import DynamicApiError
|
from kubernetes.dynamic.exceptions import DynamicApiError
|
||||||
@@ -143,6 +150,7 @@ 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
|
||||||
@@ -150,33 +158,18 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
JSON_PATCH_ARGS = {
|
JSON_PATCH_ARGS = {
|
||||||
'api_version': {
|
"api_version": {"default": "v1", "aliases": ["api", "version"]},
|
||||||
'default': 'v1',
|
"kind": {"type": "str", "required": True},
|
||||||
'aliases': ['api', 'version'],
|
"namespace": {"type": "str"},
|
||||||
},
|
"name": {"type": "str", "required": True},
|
||||||
"kind": {
|
"patch": {"type": "list", "required": True, "elements": "dict"},
|
||||||
"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
|
||||||
@@ -185,16 +178,10 @@ 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 = {
|
error = {"msg": "Invalid JSON patch", "exception": e}
|
||||||
"msg": "Invalid JSON patch",
|
|
||||||
"exception": e
|
|
||||||
}
|
|
||||||
return None, error
|
return None, error
|
||||||
except jsonpatch.JsonPatchConflict as e:
|
except jsonpatch.JsonPatchConflict as e:
|
||||||
error = {
|
error = {"msg": "Patch could not be applied due to a conflict", "exception": e}
|
||||||
"msg": "Patch could not be applied due to a conflict",
|
|
||||||
"exception": e
|
|
||||||
}
|
|
||||||
return None, error
|
return None, error
|
||||||
|
|
||||||
|
|
||||||
@@ -209,15 +196,14 @@ def execute_module(k8s_module, module):
|
|||||||
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("type"):
|
if module.params.get("wait_condition") and module.params.get("wait_condition").get(
|
||||||
wait_condition = module.params['wait_condition']
|
"type"
|
||||||
|
):
|
||||||
|
wait_condition = module.params["wait_condition"]
|
||||||
# definition is needed for wait
|
# definition is needed for wait
|
||||||
definition = {
|
definition = {
|
||||||
"kind": kind,
|
"kind": kind,
|
||||||
"metadata": {
|
"metadata": {"name": name, "namespace": namespace},
|
||||||
"name": name,
|
|
||||||
"namespace": namespace,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def build_error_msg(kind, name, msg):
|
def build_error_msg(kind, name, msg):
|
||||||
@@ -228,30 +214,50 @@ def execute_module(k8s_module, module):
|
|||||||
try:
|
try:
|
||||||
existing = resource.get(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(msg=build_error_msg(kind, name, msg), error=exc.status, status=exc.status, reason=exc.reason)
|
module.fail_json(
|
||||||
|
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(msg=build_error_msg(kind, name, msg), error='', status='', reason='')
|
module.fail_json(
|
||||||
|
msg=build_error_msg(kind, name, msg), error="", status="", reason=""
|
||||||
|
)
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode and not k8s_module.supports_dry_run:
|
||||||
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 = resource.patch(patch, name=name, namespace=namespace, content_type="application/json-patch+json").to_dict()
|
obj = resource.patch(
|
||||||
|
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(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
|
module.fail_json(
|
||||||
|
msg=msg, error=exc.status, status=exc.status, reason=exc.reason
|
||||||
|
)
|
||||||
except Exception as exc:
|
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:
|
||||||
success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
success, result["result"], result["duration"] = k8s_module.wait(
|
||||||
|
resource, definition, wait_sleep, wait_timeout, condition=wait_condition
|
||||||
|
)
|
||||||
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
||||||
result["changed"] = not match
|
result["changed"] = not match
|
||||||
if module._diff:
|
if module._diff:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -65,9 +65,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 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
|
||||||
@@ -100,9 +100,9 @@ EXAMPLES = r'''
|
|||||||
namespace: testing
|
namespace: testing
|
||||||
name: example
|
name: example
|
||||||
register: log
|
register: log
|
||||||
'''
|
"""
|
||||||
|
|
||||||
RETURN = r'''
|
RETURN = r"""
|
||||||
log:
|
log:
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
@@ -113,15 +113,20 @@ 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
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
|
AnsibleModule,
|
||||||
|
)
|
||||||
from ansible.module_utils.six import PY2
|
from ansible.module_utils.six import PY2
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
|
AUTH_ARG_SPEC,
|
||||||
|
NAME_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def argspec():
|
def argspec():
|
||||||
@@ -129,55 +134,62 @@ 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=[]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def execute_module(module, k8s_ansible_mixin):
|
def execute_module(module, k8s_ansible_mixin):
|
||||||
name = module.params.get('name')
|
name = module.params.get("name")
|
||||||
namespace = module.params.get('namespace')
|
namespace = module.params.get("namespace")
|
||||||
label_selector = ','.join(module.params.get('label_selectors', {}))
|
label_selector = ",".join(module.params.get("label_selectors", {}))
|
||||||
if name and label_selector:
|
if name and label_selector:
|
||||||
module.fail(msg='Only one of name or label_selectors can be provided')
|
module.fail(msg="Only one of name or label_selectors can be provided")
|
||||||
|
|
||||||
resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
|
resource = k8s_ansible_mixin.find_resource(
|
||||||
v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
|
module.params["kind"], module.params["api_version"], fail=True
|
||||||
|
)
|
||||||
|
v1_pods = k8s_ansible_mixin.find_resource("Pod", "v1", fail=True)
|
||||||
|
|
||||||
if 'log' not in resource.subresources:
|
if "log" not in resource.subresources:
|
||||||
if not name:
|
if not name:
|
||||||
module.fail(msg='name must be provided for resources that do not support the log subresource')
|
module.fail(
|
||||||
|
msg="name must be provided for resources that do not support the log subresource"
|
||||||
|
)
|
||||||
instance = resource.get(name=name, namespace=namespace)
|
instance = resource.get(name=name, namespace=namespace)
|
||||||
label_selector = ','.join(extract_selectors(module, 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:
|
||||||
module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
|
module.fail(
|
||||||
|
msg="No pods in namespace {0} matched selector {1}".format(
|
||||||
|
namespace, label_selector
|
||||||
|
)
|
||||||
|
)
|
||||||
# This matches the behavior of kubectl when logging pods via a selector
|
# 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
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if module.params.get('container'):
|
if module.params.get("container"):
|
||||||
kwargs['query_params'] = dict(container=module.params['container'])
|
kwargs["query_params"] = dict(container=module.params["container"])
|
||||||
|
|
||||||
if module.params.get('since_seconds'):
|
if module.params.get("since_seconds"):
|
||||||
kwargs.setdefault('query_params', {}).update({'sinceSeconds': module.params['since_seconds']})
|
kwargs.setdefault("query_params", {}).update(
|
||||||
|
{"sinceSeconds": module.params["since_seconds"]}
|
||||||
|
)
|
||||||
|
|
||||||
log = serialize_log(resource.log.get(
|
log = serialize_log(
|
||||||
name=name,
|
resource.log.get(name=name, namespace=namespace, serialize=False, **kwargs)
|
||||||
namespace=namespace,
|
)
|
||||||
serialize=False,
|
|
||||||
**kwargs
|
|
||||||
))
|
|
||||||
|
|
||||||
module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
|
module.exit_json(changed=False, log=log, log_lines=log.split("\n"))
|
||||||
|
|
||||||
|
|
||||||
def extract_selectors(module, instance):
|
def extract_selectors(module, instance):
|
||||||
@@ -185,35 +197,46 @@ def extract_selectors(module, instance):
|
|||||||
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||||
selectors = []
|
selectors = []
|
||||||
if not instance.spec.selector:
|
if not instance.spec.selector:
|
||||||
module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
|
module.fail(
|
||||||
'/'.join(instance.group, instance.apiVersion), instance.kind))
|
msg="{0} {1} does not support the log subresource directly, and no Pod selector was found on the object".format(
|
||||||
|
"/".join(instance.group, instance.apiVersion), instance.kind
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
|
if not (
|
||||||
|
instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions
|
||||||
|
):
|
||||||
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
|
# 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('{key} {operator} {values}'.format(
|
selectors.append(
|
||||||
|
"{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:
|
||||||
module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
|
module.fail(
|
||||||
|
msg="The k8s_log module does not support the {0} matchExpression operator".format(
|
||||||
|
operator.lower()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return selectors
|
return selectors
|
||||||
|
|
||||||
@@ -221,18 +244,20 @@ def extract_selectors(module, instance):
|
|||||||
def serialize_log(response):
|
def serialize_log(response):
|
||||||
if PY2:
|
if PY2:
|
||||||
return response.data
|
return response.data
|
||||||
return response.data.decode('utf8')
|
return response.data.decode("utf8")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
# 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"
|
||||||
@@ -34,18 +35,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.
|
||||||
@@ -74,25 +75,29 @@ 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 AnsibleModule
|
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, NAME_ARG_SPEC)
|
AUTH_ARG_SPEC,
|
||||||
|
NAME_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_managed_resource(module):
|
def get_managed_resource(module):
|
||||||
managed_resource = {}
|
managed_resource = {}
|
||||||
|
|
||||||
kind = module.params['kind']
|
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:
|
||||||
module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
|
module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind))
|
||||||
return managed_resource
|
return managed_resource
|
||||||
@@ -102,80 +107,100 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
results = []
|
results = []
|
||||||
|
|
||||||
resources = k8s_ansible_mixin.kubernetes_facts(
|
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"],
|
||||||
|
)
|
||||||
|
|
||||||
for resource in resources['resources']:
|
changed = False
|
||||||
|
for resource in resources["resources"]:
|
||||||
result = perform_action(module, k8s_ansible_mixin, resource)
|
result = perform_action(module, k8s_ansible_mixin, resource)
|
||||||
|
changed = result["changed"] or changed
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
module.exit_json(**{
|
module.exit_json(**{"changed": changed, "rollback_info": results})
|
||||||
'changed': True,
|
|
||||||
'rollback_info': results
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def perform_action(module, k8s_ansible_mixin, resource):
|
def perform_action(module, k8s_ansible_mixin, resource):
|
||||||
if module.params['kind'] == "DaemonSet":
|
if module.params["kind"] == "DaemonSet":
|
||||||
current_revision = resource['metadata']['generation']
|
current_revision = resource["metadata"]["generation"]
|
||||||
elif module.params['kind'] == "Deployment":
|
elif module.params["kind"] == "Deployment":
|
||||||
current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
current_revision = resource["metadata"]["annotations"][
|
||||||
|
"deployment.kubernetes.io/revision"
|
||||||
|
]
|
||||||
|
|
||||||
managed_resource = get_managed_resource(module)
|
managed_resource = get_managed_resource(module)
|
||||||
managed_resources = k8s_ansible_mixin.kubernetes_facts(
|
managed_resources = k8s_ansible_mixin.kubernetes_facts(
|
||||||
managed_resource['kind'],
|
managed_resource["kind"],
|
||||||
managed_resource['api_version'],
|
managed_resource["api_version"],
|
||||||
'',
|
"",
|
||||||
module.params['namespace'],
|
module.params["namespace"],
|
||||||
resource['spec']
|
resource["spec"]["selector"]["matchLabels"],
|
||||||
['selector']
|
"",
|
||||||
['matchLabels'],
|
)
|
||||||
'')
|
|
||||||
|
|
||||||
prev_managed_resource = get_previous_revision(managed_resources['resources'],
|
prev_managed_resource = get_previous_revision(
|
||||||
current_revision)
|
managed_resources["resources"], current_revision
|
||||||
|
)
|
||||||
|
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":
|
if module.params["kind"] == "Deployment":
|
||||||
del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
|
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
||||||
|
"pod-template-hash"
|
||||||
|
]
|
||||||
|
|
||||||
resource_patch = [{
|
resource_patch = [
|
||||||
|
{
|
||||||
"op": "replace",
|
"op": "replace",
|
||||||
"path": "/spec/template",
|
"path": "/spec/template",
|
||||||
"value": prev_managed_resource['spec']['template']
|
"value": prev_managed_resource["spec"]["template"],
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"op": "replace",
|
"op": "replace",
|
||||||
"path": "/metadata/annotations",
|
"path": "/metadata/annotations",
|
||||||
"value": {
|
"value": {
|
||||||
"deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
"deployment.kubernetes.io/revision": prev_managed_resource[
|
||||||
}
|
"metadata"
|
||||||
}]
|
]["annotations"]["deployment.kubernetes.io/revision"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
api_target = 'deployments'
|
api_target = "deployments"
|
||||||
content_type = 'application/json-patch+json'
|
content_type = "application/json-patch+json"
|
||||||
elif module.params['kind'] == "DaemonSet":
|
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
|
||||||
|
if not module.check_mode:
|
||||||
rollback = k8s_ansible_mixin.client.request(
|
rollback = k8s_ansible_mixin.client.request(
|
||||||
"PATCH",
|
"PATCH",
|
||||||
"/apis/{0}/namespaces/{1}/{2}/{3}"
|
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
||||||
.format(module.params['api_version'],
|
module.params["api_version"],
|
||||||
module.params['namespace'],
|
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.to_dict()
|
result["resources"] = rollback
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -184,8 +209,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
|
||||||
@@ -193,27 +218,40 @@ 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 int(resource['metadata']
|
if (
|
||||||
['annotations']
|
int(
|
||||||
['deployment.kubernetes.io/revision']) == int(current_revision) - 1:
|
resource["metadata"]["annotations"][
|
||||||
|
"deployment.kubernetes.io/revision"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
== int(current_revision) - 1
|
||||||
|
):
|
||||||
return resource
|
return resource
|
||||||
elif resource['kind'] == 'ControllerRevision':
|
elif resource["kind"] == "ControllerRevision":
|
||||||
if int(resource['metadata']
|
if (
|
||||||
['annotations']
|
int(
|
||||||
['deprecated.daemonset.template.generation']) == int(current_revision) - 1:
|
resource["metadata"]["annotations"][
|
||||||
|
"deprecated.daemonset.template.generation"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
== int(current_revision) - 1
|
||||||
|
):
|
||||||
return resource
|
return resource
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (K8sAnsibleMixin, get_api_client)
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
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,133 +139,170 @@ 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
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||||
|
AnsibleModule,
|
||||||
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC)
|
AUTH_ARG_SPEC,
|
||||||
|
RESOURCE_ARG_SPEC,
|
||||||
|
NAME_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SCALE_ARG_SPEC = {
|
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(module, k8s_ansible_mixin,):
|
def execute_module(
|
||||||
|
module,
|
||||||
|
k8s_ansible_mixin,
|
||||||
|
):
|
||||||
k8s_ansible_mixin.set_resource_definitions(module)
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
|
|
||||||
definition = k8s_ansible_mixin.resource_definitions[0]
|
definition = k8s_ansible_mixin.resource_definitions[0]
|
||||||
|
|
||||||
name = definition['metadata']['name']
|
name = definition["metadata"]["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')
|
current_replicas = module.params.get("current_replicas")
|
||||||
replicas = module.params.get('replicas')
|
replicas = module.params.get("replicas")
|
||||||
resource_version = module.params.get('resource_version')
|
resource_version = module.params.get("resource_version")
|
||||||
|
|
||||||
label_selectors = module.params.get('label_selectors')
|
label_selectors = module.params.get("label_selectors")
|
||||||
if not label_selectors:
|
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 = module.params.get("wait")
|
||||||
wait_time = module.params.get('wait_timeout')
|
wait_time = module.params.get("wait_timeout")
|
||||||
wait_sleep = module.params.get('wait_sleep')
|
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)
|
resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True)
|
||||||
|
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import NotFoundError
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
NotFoundError,
|
||||||
|
)
|
||||||
|
|
||||||
multiple_scale = False
|
multiple_scale = False
|
||||||
try:
|
try:
|
||||||
existing = resource.get(name=name, namespace=namespace, label_selector=','.join(label_selectors))
|
existing = resource.get(
|
||||||
if existing.kind.endswith('List'):
|
name=name, namespace=namespace, label_selector=",".join(label_selectors)
|
||||||
|
)
|
||||||
|
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 exc:
|
except NotFoundError as exc:
|
||||||
module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
module.fail_json(
|
||||||
error=exc.value.get('status'))
|
msg="Failed to retrieve requested object: {0}".format(exc),
|
||||||
|
error=exc.value.get("status"),
|
||||||
|
)
|
||||||
|
|
||||||
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 module.params['kind'] == 'job':
|
if module.params["kind"].lower() == "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, existing.metadata.resourceVersion, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
resource_version,
|
||||||
|
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, existing_count, existing.kind, existing.metadata.name, existing.metadata.namespace)
|
current_replicas,
|
||||||
|
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 not module.check_mode:
|
if module.params["kind"].lower() == "job":
|
||||||
if module.params['kind'] == 'job':
|
|
||||||
existing.spec.parallelism = replicas
|
existing.spec.parallelism = replicas
|
||||||
result = resource.patch(existing.to_dict()).to_dict()
|
result = {"changed": True}
|
||||||
|
if module.check_mode:
|
||||||
|
result["result"] = existing.to_dict()
|
||||||
else:
|
else:
|
||||||
result = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time, wait_sleep)
|
result["result"] = resource.patch(existing.to_dict()).to_dict()
|
||||||
changed = changed or result['changed']
|
else:
|
||||||
|
result = scale(
|
||||||
|
module,
|
||||||
|
k8s_ansible_mixin,
|
||||||
|
resource,
|
||||||
|
existing,
|
||||||
|
replicas,
|
||||||
|
wait,
|
||||||
|
wait_time,
|
||||||
|
wait_sleep,
|
||||||
|
)
|
||||||
|
changed = changed or result["changed"]
|
||||||
else:
|
else:
|
||||||
name = existing.metadata.name
|
name = existing.metadata.name
|
||||||
namespace = existing.metadata.namespace
|
namespace = existing.metadata.namespace
|
||||||
existing = resource.get(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)
|
||||||
|
|
||||||
@@ -277,56 +314,87 @@ 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(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time, wait_sleep):
|
def scale(
|
||||||
|
module,
|
||||||
|
k8s_ansible_mixin,
|
||||||
|
resource,
|
||||||
|
existing_object,
|
||||||
|
replicas,
|
||||||
|
wait,
|
||||||
|
wait_time,
|
||||||
|
wait_sleep,
|
||||||
|
):
|
||||||
name = existing_object.metadata.name
|
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"):
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="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 = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
|
scale_obj = {
|
||||||
|
"kind": kind,
|
||||||
|
"metadata": {"name": name, "namespace": namespace},
|
||||||
|
"spec": {"replicas": replicas},
|
||||||
|
}
|
||||||
|
|
||||||
existing = resource.get(name=name, namespace=namespace)
|
existing = resource.get(name=name, namespace=namespace)
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
if module.check_mode:
|
||||||
|
k8s_obj = copy.deepcopy(existing.to_dict())
|
||||||
|
k8s_obj["spec"]["replicas"] = replicas
|
||||||
|
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
||||||
|
if wait:
|
||||||
|
result["duration"] = 0
|
||||||
|
result["result"] = k8s_obj
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
resource.scale.patch(body=scale_obj)
|
resource.scale.patch(body=scale_obj)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
module.fail_json(msg="Scale request failed: {0}".format(exc))
|
module.fail_json(msg="Scale request failed: {0}".format(exc))
|
||||||
|
|
||||||
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
|
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
|
||||||
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
result["result"] = k8s_obj
|
||||||
result = dict()
|
if wait and not module.check_mode:
|
||||||
result['result'] = k8s_obj
|
success, result["result"], result["duration"] = k8s_ansible_mixin.wait(
|
||||||
result['changed'] = not match
|
resource, scale_obj, wait_sleep, wait_time
|
||||||
if module._diff:
|
)
|
||||||
result['diff'] = diffs
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
success, result['result'], result['duration'] = k8s_ansible_mixin.wait(resource, scale_obj, wait_sleep, wait_time)
|
|
||||||
if not success:
|
if not success:
|
||||||
module.fail_json(msg="Resource scaling timed out", **result)
|
module.fail_json(msg="Resource scaling timed out", **result)
|
||||||
|
|
||||||
|
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
||||||
|
result["changed"] = not match
|
||||||
|
if module._diff:
|
||||||
|
result["diff"] = diffs
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
('resource_definition', 'src'),
|
("resource_definition", "src"),
|
||||||
]
|
]
|
||||||
module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
|
module = AnsibleModule(
|
||||||
|
argument_spec=argspec(),
|
||||||
|
mutually_exclusive=mutually_exclusive,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
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,32 +140,36 @@ 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 AnsibleModule
|
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, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
|
AUTH_ARG_SPEC,
|
||||||
|
COMMON_ARG_SPEC,
|
||||||
|
RESOURCE_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
SERVICE_ARG_SPEC = {
|
SERVICE_ARG_SPEC = {
|
||||||
'apply': {
|
"apply": {"type": "bool", "default": False},
|
||||||
'type': 'bool',
|
"name": {"required": True},
|
||||||
'default': False,
|
"namespace": {"required": True},
|
||||||
|
"merge_type": {
|
||||||
|
"type": "list",
|
||||||
|
"elements": "str",
|
||||||
|
"choices": ["json", "merge", "strategic-merge"],
|
||||||
},
|
},
|
||||||
'name': {'required': True},
|
"selector": {"type": "dict"},
|
||||||
'namespace': {'required': True},
|
"type": {
|
||||||
'merge_type': {'type': 'list', 'elements': 'str', 'choices': ['json', 'merge', 'strategic-merge']},
|
"type": "str",
|
||||||
'selector': {'type': 'dict'},
|
"choices": ["NodePort", "ClusterIP", "LoadBalancer", "ExternalName"],
|
||||||
'type': {
|
|
||||||
'type': 'str',
|
|
||||||
'choices': [
|
|
||||||
'NodePort', 'ClusterIP', 'LoadBalancer', 'ExternalName'
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'ports': {'type': 'list', 'elements': 'dict'},
|
"ports": {"type": "list", "elements": "dict"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -195,29 +199,31 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
"""Module execution"""
|
"""Module execution"""
|
||||||
k8s_ansible_mixin.set_resource_definitions(module)
|
k8s_ansible_mixin.set_resource_definitions(module)
|
||||||
|
|
||||||
api_version = 'v1'
|
api_version = "v1"
|
||||||
selector = module.params.get('selector')
|
selector = module.params.get("selector")
|
||||||
service_type = module.params.get('type')
|
service_type = module.params.get("type")
|
||||||
ports = module.params.get('ports')
|
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")
|
||||||
|
|
||||||
# 'resource_definition:' has lower priority than module parameters
|
# 'resource_definition:' has lower priority than module parameters
|
||||||
definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
|
definition = dict(
|
||||||
|
merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition)
|
||||||
|
)
|
||||||
|
|
||||||
resource = k8s_ansible_mixin.find_resource('Service', api_version, fail=True)
|
resource = k8s_ansible_mixin.find_resource("Service", api_version, fail=True)
|
||||||
definition = k8s_ansible_mixin.set_defaults(resource, definition)
|
definition = k8s_ansible_mixin.set_defaults(resource, definition)
|
||||||
result = k8s_ansible_mixin.perform_action(resource, definition)
|
result = k8s_ansible_mixin.perform_action(resource, definition)
|
||||||
|
|
||||||
@@ -227,12 +233,14 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
|
||||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
K8sAnsibleMixin, get_api_client)
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||||
execute_module(module, k8s_ansible_mixin)
|
execute_module(module, k8s_ansible_mixin)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
313
plugins/modules/k8s_taint.py
Normal file
313
plugins/modules/k8s_taint.py
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
#!/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.basic import AnsibleModule
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin,
|
||||||
|
get_api_client,
|
||||||
|
)
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||||
|
AUTH_ARG_SPEC,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from kubernetes.client.api import core_v1_api
|
||||||
|
from kubernetes.client.exceptions import ApiException
|
||||||
|
except ImportError:
|
||||||
|
# ImportError are managed by the common module already.
|
||||||
|
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):
|
||||||
|
self.module = module
|
||||||
|
self.k8s_ansible_mixin = K8sAnsibleMixin(module=self.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()
|
||||||
|
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 = AnsibleModule(
|
||||||
|
argument_spec=argspec(),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
k8s_taint = K8sTaintAnsible(module)
|
||||||
|
k8s_taint.execute_module()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
collections:
|
|
||||||
- name: cloud.common
|
|
||||||
version: ">=2.0.4"
|
|
||||||
8
tests/integration/targets/helm/aliases
Normal file
8
tests/integration/targets/helm/aliases
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# slow - 11min
|
||||||
|
slow
|
||||||
|
time=313
|
||||||
|
helm_info
|
||||||
|
helm_plugin
|
||||||
|
helm_plugin_info
|
||||||
|
helm_repository
|
||||||
|
helm_template
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
---
|
---
|
||||||
helm_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"
|
||||||
helm_namespace: helm
|
|
||||||
|
|
||||||
tiller_namespace: tiller
|
|
||||||
tiller_cluster_role: cluster-admin
|
|
||||||
|
|
||||||
chart_test: "ingress-nginx"
|
chart_test: "ingress-nginx"
|
||||||
chart_test_local_path: "nginx-ingress"
|
chart_test_local_path: "nginx-ingress"
|
||||||
@@ -17,3 +13,15 @@ chart_test_git_repo: "http://github.com/helm/charts.git"
|
|||||||
chart_test_values:
|
chart_test_values:
|
||||||
revisionHistoryLimit: 0
|
revisionHistoryLimit: 0
|
||||||
myValue: "changed"
|
myValue: "changed"
|
||||||
|
|
||||||
|
test_namespace:
|
||||||
|
- "helm-diff"
|
||||||
|
- "helm-envvars"
|
||||||
|
- "helm-uninstall"
|
||||||
|
- "helm-not-installed"
|
||||||
|
- "helm-crd"
|
||||||
|
- "helm-url"
|
||||||
|
- "helm-repository"
|
||||||
|
- "helm-local-path-001"
|
||||||
|
- "helm-local-path-002"
|
||||||
|
- "helm-local-path-003"
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
apiVersion: apiextensions.k8s.io/v1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: foos.example.com
|
name: foos.ansible.com
|
||||||
spec:
|
spec:
|
||||||
group: example.com
|
group: ansible.com
|
||||||
versions:
|
versions:
|
||||||
- name: v1
|
- name: v1
|
||||||
served: true
|
served: true
|
||||||
96
tests/integration/targets/helm/library/helm_test_version.py
Normal file
96
tests/integration/targets/helm/library/helm_test_version.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2021, 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_test_version
|
||||||
|
short_description: check helm executable version
|
||||||
|
author:
|
||||||
|
- Aubin Bikouo (@abikouo)
|
||||||
|
requirements:
|
||||||
|
- "helm (https://github.com/helm/helm/releases)"
|
||||||
|
description:
|
||||||
|
- validate version of helm binary is lower than the specified version.
|
||||||
|
options:
|
||||||
|
binary_path:
|
||||||
|
description:
|
||||||
|
- The path of a helm binary to use.
|
||||||
|
required: false
|
||||||
|
type: path
|
||||||
|
version:
|
||||||
|
description:
|
||||||
|
- version to test against helm binary.
|
||||||
|
type: str
|
||||||
|
default: 3.7.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: validate helm binary version is lower than 3.5.0
|
||||||
|
helm_test_version:
|
||||||
|
binary_path: path/to/helm
|
||||||
|
version: "3.5.0"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = r"""
|
||||||
|
message:
|
||||||
|
type: str
|
||||||
|
description: Text message describing the test result.
|
||||||
|
returned: always
|
||||||
|
sample: 'version installed: 3.4.5 is lower than version 3.5.0'
|
||||||
|
result:
|
||||||
|
type: bool
|
||||||
|
description: Test result.
|
||||||
|
returned: always
|
||||||
|
sample: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
||||||
|
LooseVersion,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
binary_path=dict(type="path"),
|
||||||
|
version=dict(type="str", default="3.7.0"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
bin_path = module.params.get("binary_path")
|
||||||
|
version = module.params.get("version")
|
||||||
|
|
||||||
|
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)
|
||||||
|
rc, out, err = module.run_command([helm_cmd_common, "version"])
|
||||||
|
if rc != 0:
|
||||||
|
module.fail_json(msg="helm version failed.", err=err, out=out, rc=rc)
|
||||||
|
|
||||||
|
m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
|
||||||
|
installed_version = m.group(1)
|
||||||
|
|
||||||
|
message = "version installed: %s" % installed_version
|
||||||
|
if LooseVersion(installed_version) < LooseVersion(version):
|
||||||
|
message += " is lower than version %s" % version
|
||||||
|
module.exit_json(changed=False, result=True, message=message)
|
||||||
|
else:
|
||||||
|
message += " is greater than version %s" % version
|
||||||
|
module.exit_json(changed=False, result=False, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user