Compare commits

..

1 Commits

Author SHA1 Message Date
abikouo
4b8b3fa1ee [backport/2.1] molecule to ansible-test CI migration (#398)
integration testing migration from molecule to ansible-test
2022-03-08 17:25:50 +01:00
246 changed files with 2675 additions and 15802 deletions

View File

@@ -1,4 +0,0 @@
---
backport_branch_prefix: patchback/backports/
backport_label_prefix: backport-
target_branch_prefix: stable-

1
.gitignore vendored
View File

@@ -14,7 +14,6 @@ tests/output
tests/integration/cloud-config-* tests/integration/cloud-config-*
.cache .cache
# Helm charts
tests/integration/*-chart-*.tgz tests/integration/*-chart-*.tgz
# ansible-test generated file # ansible-test generated file

View File

@@ -16,5 +16,3 @@ rules:
indent-sequences: consistent indent-sequences: consistent
ignore: | ignore: |
.cache .cache
.tox
tests/output

View File

@@ -5,95 +5,6 @@ Kubernetes Collection Release Notes
.. contents:: Topics .. contents:: Topics
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
======
Minor Changes
-------------
- add support for in-memory kubeconfig in addition to file for k8s modules. (https://github.com/ansible-collections/kubernetes.core/pull/212).
- helm - add support for history_max cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/164).
- k8s - add support for label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/43).
- k8s - add support for waiting on statefulsets (https://github.com/ansible-collections/kubernetes.core/pull/195).
- k8s_log - Add since-seconds parameter to the k8s_log module (https://github.com/ansible-collections/kubernetes.core/pull/142).
- new lookup plugin to support kubernetes kustomize feature. (https://github.com/ansible-collections/kubernetes.core/issues/39).
- re-enable turbo mode for collection. The default is initially set to off (https://github.com/ansible-collections/kubernetes.core/pull/169).
Bugfixes
--------
- common - import k8sdynamicclient directly to workaround Ansible upstream bug (https://github.com/ansible-collections/kubernetes.core/issues/162).
- connection plugin - add arguments information into censored command (https://github.com/ansible-collections/kubernetes.core/pull/196).
- fix resource cache not being used (https://github.com/ansible-collections/kubernetes.core/pull/228).
- k8s - Fixes a bug where diff was always returned when using apply or modifying an existing object, even when diff=no was specified. The module no longer returns diff unless requested and will now honor diff=no (https://github.com/ansible-collections/kubernetes.core/pull/146).
- k8s_cp - fix k8s_cp uploading when target container's WORKDIR is not '/' (https://github.com/ansible-collections/kubernetes.core/issues/222).
- k8s_exec - add missing deprecation notice to return_code for k8s_exec (https://github.com/ansible-collections/kubernetes.core/pull/233).
- k8s_exec - fix k8s_exec returning rc attribute, to follow ansible's common return values (https://github.com/ansible-collections/kubernetes.core/pull/230).
- lookup - recommend query instead of lookup (https://github.com/ansible-collections/kubernetes.core/issues/147).
- support the ``template`` param in all collections depending on kubernetes.core (https://github.com/ansible-collections/kubernetes.core/pull/154).
New Plugins
-----------
Lookup
~~~~~~
- kustomize - Build a set of kubernetes resources using a 'kustomization.yaml' file.
New Modules
-----------
- k8s_cp - Copy files and directories to and from pod.
- k8s_drain - Drain, Cordon, or Uncordon node in k8s cluster
v2.1.1 v2.1.1
====== ======

View File

@@ -1,8 +1,8 @@
# Also needs to be updated in galaxy.yml # Also needs to be updated in galaxy.yml
VERSION = 2.3.0 VERSION = 2.1.1
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("{0}.{1}".format(platform.python_version_tuple()[0], platform.python_version_tuple()[1]))'`
clean: clean:
rm -f kubernetes-core-${VERSION}.tar.gz rm -f kubernetes-core-${VERSION}.tar.gz
@@ -22,7 +22,10 @@ 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 --diff --no-temp-workdir --color --skip-tags False --retry-on-error --continue-on-error --python $(PYTHON_VERSION) -v --coverage $(?TEST_ARGS) ansible-test integration --docker -v --color --retry-on-error --python $(PYTHON_VERSION) --continue-on-error --diff --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)

View File

@@ -2,7 +2,7 @@
[![CI](https://github.com/ansible-collections/kubernetes.core/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/kubernetes.core/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/kubernetes.core)](https://codecov.io/gh/ansible-collections/kubernetes.core) [![CI](https://github.com/ansible-collections/kubernetes.core/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/kubernetes.core/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/kubernetes.core)](https://codecov.io/gh/ansible-collections/kubernetes.core)
This repository hosts the `kubernetes.core` (formerly known as `community.kubernetes`) Ansible Collection. This repo hosts the `kubernetes.core` (formerly known as `community.kubernetes`) Ansible Collection.
The collection includes a variety of Ansible content to help automate the management of applications in Kubernetes and OpenShift clusters, as well as the provisioning and maintenance of clusters themselves. The collection includes a variety of Ansible content to help automate the management of applications in Kubernetes and OpenShift clusters, as well as the provisioning and maintenance of clusters themselves.
@@ -22,10 +22,6 @@ PEP440 is the schema used to describe the versions of Ansible.
Note: Python2 is deprecated from [1st January 2020](https://www.python.org/doc/sunset-python-2/). Please switch to Python3. Note: Python2 is deprecated from [1st January 2020](https://www.python.org/doc/sunset-python-2/). Please switch to Python3.
## Kubernetes Version Support
This collection supports Kubernetes versions >=1.19.
## Included content ## Included content
Click on the name of a plugin or module to view that content's documentation: Click on the name of a plugin or module to view that content's documentation:
@@ -50,7 +46,6 @@ Name | Description
Name | Description Name | Description
--- | --- --- | ---
[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_lookup.rst)|Query the K8s API [kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_lookup.rst)|Query the K8s API
[kubernetes.core.kustomize](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.kustomize_lookup.rst)|Build a set of kubernetes resources using a 'kustomization.yaml' file.
### Modules ### Modules
Name | Description Name | Description
@@ -63,8 +58,6 @@ Name | Description
[kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates [kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates
[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_module.rst)|Manage Kubernetes (K8s) objects [kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_module.rst)|Manage Kubernetes (K8s) objects
[kubernetes.core.k8s_cluster_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_cluster_info_module.rst)|Describe Kubernetes (K8s) cluster, APIs available and their respective versions [kubernetes.core.k8s_cluster_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_cluster_info_module.rst)|Describe Kubernetes (K8s) cluster, APIs available and their respective versions
[kubernetes.core.k8s_cp](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_cp_module.rst)|Copy files and directories to and from pod.
[kubernetes.core.k8s_drain](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_drain_module.rst)|Drain, Cordon, or Uncordon node in k8s cluster
[kubernetes.core.k8s_exec](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_exec_module.rst)|Execute command in Pod [kubernetes.core.k8s_exec](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_exec_module.rst)|Execute command in Pod
[kubernetes.core.k8s_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_info_module.rst)|Describe Kubernetes (K8s) objects [kubernetes.core.k8s_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_info_module.rst)|Describe Kubernetes (K8s) objects
[kubernetes.core.k8s_json_patch](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_json_patch_module.rst)|Apply JSON patch operations to existing objects [kubernetes.core.k8s_json_patch](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_json_patch_module.rst)|Apply JSON patch operations to existing objects
@@ -72,7 +65,6 @@ Name | Description
[kubernetes.core.k8s_rollback](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_rollback_module.rst)|Rollback Kubernetes (K8S) Deployments and DaemonSets [kubernetes.core.k8s_rollback](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_rollback_module.rst)|Rollback Kubernetes (K8S) Deployments and DaemonSets
[kubernetes.core.k8s_scale](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_scale_module.rst)|Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job. [kubernetes.core.k8s_scale](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_scale_module.rst)|Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
[kubernetes.core.k8s_service](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_service_module.rst)|Manage Services on Kubernetes [kubernetes.core.k8s_service](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_service_module.rst)|Manage Services on Kubernetes
[kubernetes.core.k8s_taint](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_taint_module.rst)|Taint a node in a Kubernetes/OpenShift cluster
<!--end collection content--> <!--end collection content-->
@@ -90,7 +82,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.3.0 version: 2.1.1
``` ```
### Installing the Kubernetes Python Library ### Installing the Kubernetes Python Library
@@ -167,25 +159,6 @@ If upgrading older playbooks which were built prior to Ansible 2.10 and this col
For documentation on how to use individual modules and other content included in this collection, please see the links in the 'Included content' section earlier in this README. For documentation on how to use individual modules and other content included in this collection, please see the links in the 'Included content' section earlier in this README.
## Ansible Turbo mode Tech Preview
The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode for modules, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
```yaml
---
- hosts: remote
environment:
ENABLE_TURBO_MODE: 1
tasks:
...
```
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).
## Testing and Development ## Testing and Development
If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there. If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there.

View File

@@ -1,2 +1 @@
kubernetes-client [platform:fedora] kubernetes-client [platform:fedora]
openshift-clients [platform:rhel-8]

View File

@@ -429,142 +429,3 @@ releases:
fragments: fragments:
- 151-check-auth-params-for-existence.yaml - 151-check-auth-params-for-existence.yaml
release_date: '2021-06-24' release_date: '2021-06-24'
2.2.0:
changes:
bugfixes:
- common - import k8sdynamicclient directly to workaround Ansible upstream bug
(https://github.com/ansible-collections/kubernetes.core/issues/162).
- connection plugin - add arguments information into censored command (https://github.com/ansible-collections/kubernetes.core/pull/196).
- fix resource cache not being used (https://github.com/ansible-collections/kubernetes.core/pull/228).
- k8s - Fixes a bug where diff was always returned when using apply or modifying
an existing object, even when diff=no was specified. The module no longer
returns diff unless requested and will now honor diff=no (https://github.com/ansible-collections/kubernetes.core/pull/146).
- k8s_cp - fix k8s_cp uploading when target container's WORKDIR is not '/' (https://github.com/ansible-collections/kubernetes.core/issues/222).
- k8s_exec - add missing deprecation notice to return_code for k8s_exec (https://github.com/ansible-collections/kubernetes.core/pull/233).
- k8s_exec - fix k8s_exec returning rc attribute, to follow ansible's common
return values (https://github.com/ansible-collections/kubernetes.core/pull/230).
- lookup - recommend query instead of lookup (https://github.com/ansible-collections/kubernetes.core/issues/147).
- support the ``template`` param in all collections depending on kubernetes.core
(https://github.com/ansible-collections/kubernetes.core/pull/154).
minor_changes:
- add support for in-memory kubeconfig in addition to file for k8s modules.
(https://github.com/ansible-collections/kubernetes.core/pull/212).
- helm - add support for history_max cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/164).
- k8s - add support for label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/43).
- k8s - add support for waiting on statefulsets (https://github.com/ansible-collections/kubernetes.core/pull/195).
- k8s_log - Add since-seconds parameter to the k8s_log module (https://github.com/ansible-collections/kubernetes.core/pull/142).
- new lookup plugin to support kubernetes kustomize feature. (https://github.com/ansible-collections/kubernetes.core/issues/39).
- re-enable turbo mode for collection. The default is initially set to off (https://github.com/ansible-collections/kubernetes.core/pull/169).
fragments:
- 142-add-sinceseconds-param-for-logs.yaml
- 146-k8s-add-support-diff-mode.yml
- 147_lookup.yml
- 154-template-param-support.yaml
- 158-k8s-add-support-label_selectors.yml
- 162_import_error.yml
- 164-add-history-max.yaml
- 169-reenable-turbo-mode.yaml
- 195-k8s-add-wait-statefulsets.yml
- 196_kubectl.yaml
- 212-in-memory-kubeconfig.yml
- 223-add-deprecation-notice.yaml
- 223-k8s-cp-uploading.yaml
- 225-kustomize-lookup-plugin.yml
- 228-fix-resource-cache.yml
- 230-k8sexec-has-new-returnvalue.yml
modules:
- description: Copy files and directories to and from pod.
name: k8s_cp
namespace: ''
- description: Drain, Cordon, or Uncordon node in k8s cluster
name: k8s_drain
namespace: ''
plugins:
lookup:
- description: Build a set of kubernetes resources using a 'kustomization.yaml'
file.
name: kustomize
namespace: null
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'

View File

@@ -15,7 +15,7 @@ Following document provides overview of Ansible Turbo mode in ``kubernetes.core`
Synopsis Synopsis
-------- --------
- A brief introduction about Ansible Turbo mode in ``kuberentes.core`` collection. - A brief introduction about Ansible Turbo mode in ``kuberentes.core`` collection.
- Ansible Turbo mode is an optional performance optimization. It can be enabled by installing the cloud.common collection and setting the ``ENABLE_TURBO_MODE`` environment variable. - Ansible Turbo mode is an optional performance optimization. It can be enabled by simply installing the cloud.common collection.
Requirements Requirements
------------ ------------
@@ -24,15 +24,6 @@ The following requirement is needed on the host that executes this module.
- The ``cloud.common`` collection (https://github.com/ansible-collections/cloud.common) - The ``cloud.common`` collection (https://github.com/ansible-collections/cloud.common)
You will also need to set the environment variable ``ENABLE_TURBO_MODE=1`` on the managed host. This can be done in the same ways you would usually do so, for example::
---
- hosts: remote
environment:
ENABLE_TURBO_MODE: 1
tasks:
...
Installation Installation
------------ ------------
@@ -99,7 +90,7 @@ The background service
The daemon kills itself after 15s, and communication are done The daemon kills itself after 15s, and communication are done
through an Unix socket. through an Unix socket.
It runs in one single process and uses ``asyncio`` internally. It runs in one single process and uses ``asyncio`` internally.
Consequently you can use the ``async`` keyword in your Ansible module. Consequently you can use the ``sync`` keyword in your Ansible module.
This will be handy if you interact with a lot of remote systems This will be handy if you interact with a lot of remote systems
at the same time. at the same time.

View File

@@ -1,5 +0,0 @@
---
sections:
- title: Scenario Guide
toctree:
- scenario_guide

View File

@@ -1,51 +0,0 @@
.. _ansible_collections.kubernetes.core.docsite.k8s_ansible_intro:
**************************************
Introduction to Ansible for Kubernetes
**************************************
.. contents::
:local:
Introduction
============
The `kubernetes.core collection <https://galaxy.ansible.com/kubernetes/core>`_ offers several modules and plugins for orchestrating Kubernetes.
Requirements
============
To use the modules, you'll need the following:
- Ansible 2.9.17 or latest installed
- `Kubernetes Python client <https://pypi.org/project/kubernetes/>`_ installed on the host that will execute the modules.
Installation
============
The Kubernetes modules are part of the Ansible Kubernetes collection.
To install the collection, run the following:
.. code-block:: bash
$ ansible-galaxy collection install kubernetes.core
Authenticating with the API
===========================
By default the Kubernetes Rest Client will look for ``~/.kube/config``, and if found, connect using the active context. You can override the location of the file using the ``kubeconfig`` parameter, and the context, using the ``context`` parameter.
Basic authentication is also supported using the ``username`` and ``password`` options. You can override the URL using the ``host`` parameter. Certificate authentication works through the ``ssl_ca_cert``, ``cert_file``, and ``key_file`` parameters, and for token authentication, use the ``api_key`` parameter.
To disable SSL certificate verification, set ``verify_ssl`` to false.
Reporting an issue
==================
- If you find a bug or have a suggestion regarding modules or plugins, please file issues at `Ansible Kubernetes collection <https://github.com/ansible-collections/kubernetes.core/issues>`_.
- If you find a bug regarding Kubernetes Python client, please file issues at `Kubernetes Client issues <https://github.com/kubernetes-client/python/issues>`_.
- If you find a bug regarding Kubectl binary, please file issues at `Kubectl issue tracker <https://github.com/kubernetes/kubectl/issues>`_
- If you find a bug regarding Helm binary, please file issues at `Helm issue tracker <https://github.com/helm/helm/issues>`_.

View File

@@ -1,88 +0,0 @@
.. _ansible_collections.kubernetes.core.docsite.k8s_ansible_inventory:
*****************************************
Using Kubernetes dynamic inventory plugin
*****************************************
.. contents::
:local:
Kubernetes dynamic inventory plugin
===================================
The best way to interact with your Pods is to use the Kubernetes dynamic inventory plugin, which queries Kubernetes APIs using ``kubectl`` command line available on controller node and tells Ansible what Pods can be managed.
Requirements
------------
To use the Kubernetes dynamic inventory plugins, you must install `Kubernetes Python client <https://github.com/kubernetes-client/python>`_, `kubectl <https://github.com/kubernetes/kubectl>`_ on your control node (the host running Ansible).
.. code-block:: bash
$ pip install kubernetes
Please refer to Kubernetes official documentation for `installing kubectl <https://kubernetes.io/docs/tasks/tools/install-kubectl/>`_ on the given operating systems.
To use this Kubernetes dynamic inventory plugin, you need to enable it first by specifying the following in the ``ansible.cfg`` file:
.. code-block:: ini
[inventory]
enable_plugins = kubernetes.core.k8s
Then, create a file that ends in ``.k8s.yml`` or ``.k8s.yaml`` in your working directory.
The ``kubernetes.core.k8s`` inventory plugin takes in the same authentication information as any other Kubernetes modules.
Here's an example of a valid inventory file:
.. code-block:: yaml
plugin: kubernetes.core.k8s
Executing ``ansible-inventory --list -i <filename>.k8s.yml`` will create a list of Pods that are ready to be configured using Ansible.
You can also provide the namespace to gather information about specific pods from the given namespace. For example, to gather information about Pods under the ``test`` namespace you will specify the ``namespaces`` parameter:
.. code-block:: yaml
plugin: kubernetes.core.k8s
connections:
- namespaces:
- test
Using vaulted configuration files
=================================
Since the inventory configuration file contains Kubernetes related sensitive information in plain text, a security risk, you may want to
encrypt your entire inventory configuration file.
You can encrypt a valid inventory configuration file as follows:
.. code-block:: bash
$ ansible-vault encrypt <filename>.k8s.yml
New Vault password:
Confirm New Vault password:
Encryption successful
$ echo "MySuperSecretPassw0rd!" > /path/to/vault_password_file
And you can use this vaulted inventory configuration file using:
.. code-block:: bash
$ ansible-inventory -i <filename>.k8s.yml --list --vault-password-file=/path/to/vault_password_file
.. seealso::
`Kubernetes Python client - Issue Tracker <https://github.com/kubernetes-client/python/issues>`_
The issue tracker for Kubernetes Python client
`Kubectl installation <https://kubernetes.io/docs/tasks/tools/install-kubectl/>`_
Installation guide for installing Kubectl
:ref:`working_with_playbooks`
An introduction to playbooks
:ref:`playbooks_vault`
Using Vault in playbooks

View File

@@ -1,12 +0,0 @@
.. _ansible_collections.kubernetes.core.docsite.k8s_scenarios:
********************************
Ansible for Kubernetes Scenarios
********************************
These scenarios teach you how to accomplish common Kubernetes tasks using Ansible. To get started, please select the task you want to accomplish.
.. toctree::
:maxdepth: 1
scenario_k8s_object

View File

@@ -1,175 +0,0 @@
.. _ansible_collections.kubernetes.core.docsite.k8s_object_template:
*******************
Creating K8S object
*******************
.. contents::
:local:
Introduction
============
This guide will show you how to utilize Ansible to create Kubernetes objects such as Pods, Deployments, and Secrets.
Scenario Requirements
=====================
* Software
* Ansible 2.9.17 or later must be installed
* The Python module ``kubernetes`` must be installed on the Ansible controller (or Target host if not executing against localhost)
* Kubernetes Cluster
* Kubectl binary installed on the Ansible controller
* Access / Credentials
* Kubeconfig configured with the given Kubernetes cluster
Assumptions
===========
- User has required level of authorization to create, delete and update resources on the given Kubernetes cluster.
Caveats
=======
- community.kubernetes 2.0.0 has been renamed to `kubernetes.core <https://github.com/ansible-collections/kubernetes.core>`_
Example Description
===================
In this use case / example, we will create a Pod in the given Kubernetes Cluster. The following Ansible playbook showcases the basic parameters that are needed for this.
.. code:: yaml
---
- hosts: localhost
collections:
- kubernetes.core
tasks:
- name: Create a pod
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Pod
metadata:
name: "utilitypod-1"
namespace: default
labels:
app: galaxy
spec:
containers:
- name: utilitypod
image: busybox
Since Ansible utilizes the Kubernetes API to perform actions, in this use case we will be connecting directly to the Kubernetes cluster.
To begin, there are a few bits of information we will need. Here you are using Kubeconfig which is pre-configured in your machine. The Kubeconfig is generally located at ``~/.kube/config``. It is highly recommended to store sensitive information such as password, user certificates in a more secure fashion using :ref:`ansible-vault` or using `Ansible Tower credentials <https://docs.ansible.com/ansible-tower/latest/html/userguide/credentials.html>`_.
Now you need to supply the information about the Pod which will be created. Using ``definition`` parameter of the ``kubernetes.core.k8s`` module, you specify `PodTemplate <https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates>`_. This PodTemplate is identical to what you provide to the ``kubectl`` command.
What to expect
--------------
- You will see a bit of JSON output after this playbook completes. This output shows various parameters that are returned from the module and from cluster about the newly created Pod.
.. code:: json
{
"changed": true,
"method": "create",
"result": {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"creationTimestamp": "2020-10-03T15:36:25Z",
"labels": {
"app": "galaxy"
},
"name": "utilitypod-1",
"namespace": "default",
"resourceVersion": "4511073",
"selfLink": "/api/v1/namespaces/default/pods/utilitypod-1",
"uid": "c7dec819-09df-4efd-9d78-67cf010b4f4e"
},
"spec": {
"containers": [{
"image": "busybox",
"imagePullPolicy": "Always",
"name": "utilitypod",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [{
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
"name": "default-token-6j842",
"readOnly": true
}]
}],
"dnsPolicy": "ClusterFirst",
"enableServiceLinks": true,
"priority": 0,
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "default",
"serviceAccountName": "default",
"terminationGracePeriodSeconds": 30,
"tolerations": [{
"effect": "NoExecute",
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"tolerationSeconds": 300
},
{
"effect": "NoExecute",
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"tolerationSeconds": 300
}
],
"volumes": [{
"name": "default-token-6j842",
"secret": {
"defaultMode": 420,
"secretName": "default-token-6j842"
}
}]
},
"status": {
"phase": "Pending",
"qosClass": "BestEffort"
}
}
}
- In the above example, 'changed' is ``True`` which notifies that the Pod creation started on the given cluster. This can take some time depending on your environment.
Troubleshooting
---------------
Things to inspect
- Check if the values provided for username and password are correct
- Check if the Kubeconfig is populated with correct values
.. seealso::
`Kubernetes Python client <https://github.com/kubernetes-client/python>`_
The GitHub Page of Kubernetes Python client
`Kubernetes Python client - Issue Tracker <https://github.com/kubernetes-client/python/issues>`_
The issue tracker for Kubernetes Python client
`Kubectl installation <https://kubernetes.io/docs/tasks/tools/install-kubectl/>`_
Installation guide for installing Kubectl
:ref:`working_with_playbooks`
An introduction to playbooks
:ref:`playbooks_vault`
Using Vault in playbooks

View File

@@ -1,18 +0,0 @@
.. _ansible_collections.kubernetes.core.docsite.scenario_guide:
Kubernetes Guide
================
Welcome to the Ansible for Kubernetes Guide!
The purpose of this guide is to teach you everything you need to know about using Ansible with Kubernetes.
To get started, please select one of the following topics.
.. toctree::
:maxdepth: 1
kubernetes_scenarios/k8s_intro
kubernetes_scenarios/k8s_inventory
kubernetes_scenarios/k8s_scenarios

View File

@@ -172,31 +172,6 @@ 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>
@@ -229,18 +204,11 @@ Examples
.. code-block:: yaml .. code-block:: yaml
- name: Gather information of Grafana chart inside monitoring namespace - name: Deploy latest version of Grafana chart inside monitoring namespace
kubernetes.core.helm_info: kubernetes.core.helm_info:
name: test name: test
release_namespace: monitoring release_namespace: monitoring
- name: Gather information about test-chart with pending state
kubernetes.core.helm_info:
name: test-chart
release_namespace: testenv
release_state:
- pending
Return Values Return Values

View File

@@ -231,23 +231,6 @@ Parameters
<div>Helm option to force reinstall, ignore on new install.</div> <div>Helm option to force reinstall, ignore on new install.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>history_max</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
</td>
<td>
</td>
<td>
<div>Limit the maximum number of revisions saved per release.</div>
<div>mutually exclusive with with <code>replace</code>.</div>
</td>
</tr>
<tr> <tr>
<td colspan="1"> <td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -390,7 +373,6 @@ Parameters
<td> <td>
<div>Reuse the given name, only if that name is a deleted release which remains in the history.</div> <div>Reuse the given name, only if that name is a deleted release which remains in the history.</div>
<div>This is unsafe in production environment.</div> <div>This is unsafe in production environment.</div>
<div>mutually exclusive with with <code>history_max</code>.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -413,24 +395,6 @@ Parameters
<div>Skip custom resource definitions when installing or upgrading.</div> <div>Skip custom resource definitions when installing or upgrading.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>timeout</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>A Go duration (described here <em>https://pkg.go.dev/time#ParseDuration</em>) value to wait for Kubernetes commands to complete. This defaults to 5m0s.</div>
<div>similar to <code>wait_timeout</code> but does not required <code>wait</code> to be activated.</div>
<div>Mutually exclusive with <code>wait_timeout</code>.</div>
</td>
</tr>
<tr> <tr>
<td colspan="1"> <td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -447,7 +411,7 @@ Parameters
</ul> </ul>
</td> </td>
<td> <td>
<div>Run <code>helm repo update</code> before the operation. Can be run as part of the package installation or as a separate step (see Examples).</div> <div>Run <code>helm repo update</code> before the operation. Can be run as part of the package installation or as a separate step.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -508,8 +472,7 @@ Parameters
</ul> </ul>
</td> </td>
<td> <td>
<div>When <em>release_state</em> is set to <code>present</code>, wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.</div> <div>Wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.</div>
<div>When <em>release_state</em> is set to <code>absent</code>, will wait until all the resources are deleted before returning. It will wait for as long as <em>wait_timeout</em>. This feature requires helm&gt;=3.7.0. Added in version 2.3.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -525,19 +488,12 @@ 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
@@ -587,13 +543,6 @@ Examples
state: absent state: absent
wait: true wait: true
- name: Separately update the repository cache
kubernetes.core.helm:
name: dummy
namespace: kube-system
state: absent
update_repo_cache: true
# From git # From git
- name: Git clone stable repo on HEAD - name: Git clone stable repo on HEAD
ansible.builtin.git: ansible.builtin.git:

View File

@@ -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> or <code>state=latest</code>.</div> <div>Required only if <code>state=absent</code>.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -170,23 +170,6 @@ 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>
@@ -200,12 +183,10 @@ 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>&nbsp;&larr;</div></li> <li><div style="color: blue"><b>present</b>&nbsp;&larr;</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>
@@ -256,17 +237,6 @@ 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

View File

@@ -39,22 +39,6 @@ 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">
@@ -71,59 +55,6 @@ 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>&nbsp;&larr;</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>
@@ -211,27 +142,6 @@ 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>&nbsp;&larr;</div></li>
</ul>
</td>
<td>
<div>Whether or not to verify the API server&#x27;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/>

View File

@@ -131,22 +131,6 @@ 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>
@@ -164,23 +148,6 @@ 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>
@@ -246,24 +213,6 @@ 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

View File

@@ -136,41 +136,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -196,33 +161,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</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&#x27;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&gt;=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 &quot;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&quot;</div>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -1,608 +0,0 @@
.. _kubernetes.core.k8s_cp_module:
**********************
kubernetes.core.k8s_cp
**********************
**Copy files and directories to and from pod.**
Version added: 2.2.0
.. contents::
:local:
:depth: 1
Synopsis
--------
- Use the Kubernetes Python client to copy files and directories to and from containers inside a pod.
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>container</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 the container in the pod to copy files/directories from/to.</div>
<div>Defaults to the only container if there is only one container in the pod.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>content</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>When used instead of <em>local_path</em>, sets the contents of a local file directly to the specified value.</div>
<div>Works only when <em>remote_path</em> is a file. Creates the file if it does not exist.</div>
<div>For advanced formatting or if the content contains a variable, use the <span class='module'>ansible.builtin.template</span> module.</div>
<div>Mutually exclusive with <em>local_path</em>.</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: &#x27;Group1,Group2&#x27;</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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>local_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path of the local file or directory.</div>
<div>Required when <em>state</em> is set to <code>from_pod</code>.</div>
<div>Mutually exclusive with <em>content</em>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>namespace</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 pod namespace name.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>no_preserve</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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>The copied file/directory&#x27;s ownership and permissions will not be preserved in the container.</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>
</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&#x27;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&gt;=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 &quot;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&quot;</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>pod</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 pod name.</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>remote_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>Path of the file or directory to copy.</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>to_pod</b>&nbsp;&larr;</div></li>
<li>from_pod</li>
</ul>
</td>
<td>
<div>When set to <code>to_pod</code>, the local <em>local_path</em> file or directory will be copied to <em>remote_path</em> into the pod.</div>
<div>When set to <code>from_pod</code>, the remote file or directory <em>remote_path</em> from pod will be copied locally to <em>local_path</em>.</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&#x27;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::
- the tar binary is required on the container when copying from local filesystem to pod.
- 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
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar
local_path: /tmp/foo
# kubectl cp /tmp/foo_dir some-namespace/some-pod:/tmp/bar_dir
- name: Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar_dir
local_path: /tmp/foo_dir
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar -c some-container
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
container: some-container
remote_path: /tmp/bar
local_path: /tmp/foo
no_preserve: True
state: to_pod
# kubectl cp some-namespace/some-pod:/tmp/foo /tmp/bar
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo
local_path: /tmp/bar
state: from_pod
# copy content into a file in the remote pod
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
state: to_pod
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo.txt
content: "This content will be copied into remote file"
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>result</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>message describing the copy operation successfully done.</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)

View File

@@ -1,636 +0,0 @@
.. _kubernetes.core.k8s_drain_module:
*************************
kubernetes.core.k8s_drain
*************************
**Drain, Cordon, or Uncordon node in k8s cluster**
Version added: 2.2.0
.. contents::
:local:
:depth: 1
Synopsis
--------
- Drain node in preparation for maintenance same as kubectl drain.
- Cordon will mark the node as unschedulable.
- Uncordon will mark the node as schedulable.
- The given node will be marked unschedulable to prevent new pods from arriving.
- Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
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>delete_options</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">dictionary</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify options to delete pods.</div>
<div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
</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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>disable_eviction</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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Forces drain to use delete rather than evict.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>force</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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>ignore_daemonsets</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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Ignore DaemonSet-managed pods.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>terminate_grace_period</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify how many seconds to wait before forcefully terminating.</div>
<div>If not specified, the default grace period for the object type will be used.</div>
<div>The value zero indicates delete immediately.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait_sleep</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">5</div>
</td>
<td>
<div>Number of seconds to sleep between checks.</div>
<div>Ignored if <code>wait_timeout</code> is not set.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait_timeout</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
</td>
<td>
<div>The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.</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: &#x27;Group1,Group2&#x27;</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 &gt;= 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&#x27;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&gt;=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 &quot;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&quot;</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>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>cordon</li>
<li><div style="color: blue"><b>drain</b>&nbsp;&larr;</div></li>
<li>uncordon</li>
</ul>
</td>
<td>
<div>Determines whether to drain, cordon, or uncordon node.</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&#x27;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: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
kubernetes.core.k8s_drain:
state: drain
name: foo
force: yes
- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
kubernetes.core.k8s_drain:
state: drain
name: foo
delete_options:
terminate_grace_period: 900
- name: Mark node "foo" as schedulable.
kubernetes.core.k8s_drain:
state: uncordon
name: foo
- name: Mark node "foo" as unschedulable.
kubernetes.core.k8s_drain:
state: cordon
name: foo
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>result</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 node status and the number of pods deleted.</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)

View File

@@ -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,7 +134,6 @@ 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>
@@ -167,55 +166,19 @@ 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: &#x27;Group1,Group2&#x27;</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>
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -231,26 +194,7 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -305,7 +249,7 @@ Parameters
<td> <td>
</td> </td>
<td> <td>
<div>The pod name.</div> <div>The pod name</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -322,7 +266,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 (for example, HTTP_PROXY).</div> <div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -438,7 +382,6 @@ Notes
----- -----
.. note:: .. note::
- Return code ``rc`` for the command executed is added in output in version 2.2.0, and deprecates return code ``return_code``.
- Return code ``return_code`` for the command executed is added in output in version 1.0.0. - Return code ``return_code`` for the command executed is added in output in version 1.0.0.
- The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource. - The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource.
- 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. - 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.
@@ -467,14 +410,7 @@ Examples
- name: Check last command status - name: Check last command status
debug: debug:
msg: "cmd failed" msg: "cmd failed"
when: command_status.rc != 0 when: command_status.return_code != 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"
@@ -504,23 +440,6 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
<div>The command object</div> <div>The command object</div>
<br/> <br/>
</td> </td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>rc</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
</td>
<td></td>
<td>
<div>The command status code</div>
<br/>
</td>
</tr> </tr>
<tr> <tr>
<td class="elbow-placeholder">&nbsp;</td> <td class="elbow-placeholder">&nbsp;</td>
@@ -534,7 +453,7 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
</td> </td>
<td></td> <td></td>
<td> <td>
<div>The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.</div> <div>The command status code</div>
<br/> <br/>
</td> </td>
</tr> </tr>

View File

@@ -173,41 +173,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -233,14 +198,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -295,25 +259,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>

View File

@@ -351,8 +351,8 @@ Status
Authors Authors
~~~~~~~ ~~~~~~~
- Chris Houseknecht (@chouseknecht) - Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch (@fabianvf) - Fabian von Feilitzsch <@fabianvf>
.. hint:: .. hint::

View File

@@ -18,7 +18,7 @@ Version added: 2.0.0
Synopsis Synopsis
-------- --------
- This module is used to apply RFC 6902 JSON patch operations only. - This module is used to apply RFC 6902 JSON patch operations only.
- Use the :ref:`kubernetes.core.k8s <kubernetes.core.k8s_module>` module for strategic merge or JSON merge operations. - Use the :ref:`k8s <k8s_module>` module for strategic merge or JSON merge operations.
- The jsonpatch library is required for check mode. - The jsonpatch library is required for check mode.
@@ -155,41 +155,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -213,14 +178,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -256,25 +220,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>

View File

@@ -174,41 +174,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -233,14 +198,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -295,25 +259,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -438,22 +383,6 @@ Parameters
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>since_seconds</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.2.0</div>
</td>
<td>
</td>
<td>
<div>A relative time in seconds before the current time from which to show logs.</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -528,7 +457,6 @@ Examples
kind: Deployment kind: Deployment
namespace: testing namespace: testing
name: example name: example
since_seconds: "4000"
register: log register: log
# This will get the log from a single Pod managed by this DeploymentConfig # This will get the log from a single Pod managed by this DeploymentConfig

View File

@@ -381,12 +381,6 @@ Parameters
<br/> <br/>
Notes
-----
.. note::
- While querying, please use ``query`` or ``lookup`` format with ``wantlist=True`` to provide an easier and more consistent interface. For more details, see https://docs.ansible.com/ansible/latest/plugins/lookup.html#forcing-lookups-to-return-lists-query-and-wantlist-true.
Examples Examples
@@ -396,23 +390,23 @@ Examples
- name: Fetch a list of namespaces - name: Fetch a list of namespaces
set_fact: set_fact:
projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}" projects: "{{ lookup('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
- name: Fetch all deployments - name: Fetch all deployments
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
- name: Fetch all deployments in a namespace - name: Fetch all deployments in a namespace
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
- name: Fetch a specific deployment by name - name: Fetch a specific deployment by name
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
- name: Fetch with label selector - name: Fetch with label selector
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}" service: "{{ lookup('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
# Use parameters from a YAML config # Use parameters from a YAML config
@@ -422,11 +416,11 @@ Examples
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object - name: Using the config (loaded from a file in prior task), fetch the latest version of the object
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}" service: "{{ lookup('kubernetes.core.k8s', resource_definition=config) }}"
- name: Use a config from the local filesystem - name: Use a config from the local filesystem
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', src='service.yml') }}" service: "{{ lookup('kubernetes.core.k8s', src='service.yml') }}"
@@ -438,28 +432,106 @@ 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="1">Key</th> <th colspan="2">Key</th>
<th>Returned</th> <th>Returned</th>
<th width="100%">Description</th> <th width="100%">Description</th>
</tr> </tr>
<tr> <tr>
<td colspan="1"> <td colspan="2">
<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">list</span> <span style="color: purple">complex</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;">[{&#x27;kind&#x27;: &#x27;ConfigMap&#x27;, &#x27;apiVersion&#x27;: &#x27;v1&#x27;, &#x27;metadata&#x27;: {&#x27;creationTimestamp&#x27;: &#x27;2022-03-04T13:59:49Z&#x27;, &#x27;name&#x27;: &#x27;my-config-map&#x27;, &#x27;namespace&#x27;: &#x27;default&#x27;, &#x27;resourceVersion&#x27;: &#x27;418&#x27;, &#x27;uid&#x27;: &#x27;5714b011-d090-4eac-8272-a0ea82ec0abd&#x27;}, &#x27;data&#x27;: {&#x27;key1&#x27;: &#x27;val1&#x27;}}]</div>
</td> </td>
</tr> </tr>
<tr>
<td class="elbow-placeholder">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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/>
@@ -471,8 +543,8 @@ Status
Authors Authors
~~~~~~~ ~~~~~~~
- Chris Houseknecht (@chouseknecht) - Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch (@fabianvf) - Fabian von Feilitzsch <@fabianvf>
.. hint:: .. hint::

View File

@@ -336,26 +336,6 @@ 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>
@@ -371,41 +351,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -430,31 +375,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>label_selectors</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.2.0</div>
</td>
<td>
</td>
<td>
<div>Selector (label query) to filter on.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -519,25 +446,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="3"> <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -679,63 +587,6 @@ 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 &quot;kubernetes &gt;= 19.15.0&quot;.</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>&nbsp;&larr;</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>
@@ -750,7 +601,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&#x27;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&#x27;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'>kubernetes.core.k8s</span> module.</div> <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -788,7 +639,6 @@ Parameters
<td> <td>
<div>Provide a valid YAML template definition file for an object when creating or updating.</div> <div>Provide a valid YAML template definition file for an object when creating or updating.</div>
<div>Value can be provided as string or dictionary.</div> <div>Value can be provided as string or dictionary.</div>
<div>The parameter accepts multiple template files. Added in version 2.0.0.</div>
<div>Mutually exclusive with <code>src</code> and <code>resource_definition</code>.</div> <div>Mutually exclusive with <code>src</code> and <code>resource_definition</code>.</div>
<div>Template files needs to be present on the Ansible Controller&#x27;s file system.</div> <div>Template files needs to be present on the Ansible Controller&#x27;s file system.</div>
<div>Additional parameters can be specified using dictionary.</div> <div>Additional parameters can be specified using dictionary.</div>
@@ -1125,15 +975,6 @@ Examples
variable_start_string: '[[' variable_start_string: '[['
variable_end_string: ']]' variable_end_string: ']]'
- name: Read multiple definition template file from the Ansible controller file system
kubernetes.core.k8s:
state: present
template:
- path: '/testing/deployment_one.j2'
- path: '/testing/deployment_two.j2'
variable_start_string: '[['
variable_end_string: ']]'
- name: fail on validation errors - name: fail on validation errors
kubernetes.core.k8s: kubernetes.core.k8s:
state: present state: present
@@ -1189,33 +1030,6 @@ 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

View File

@@ -172,41 +172,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -231,14 +196,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -293,25 +257,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>

View File

@@ -189,41 +189,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div> <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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>
@@ -248,14 +213,13 @@ Parameters
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -311,25 +275,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -516,7 +461,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&#x27;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&#x27;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'>kubernetes.core.k8s</span> module.</div> <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -172,55 +172,19 @@ 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: &#x27;Group1,Group2&#x27;</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>
<b>kubeconfig</b> <b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small"> <div style="font-size: small">
<span style="color: purple">raw</span> <span style="color: purple">path</span>
</div> </div>
</td> </td>
<td> <td>
</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>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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -280,25 +244,6 @@ 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&#x27;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&gt;=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 &quot;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&quot;</div>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div> <div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -487,7 +432,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&#x27;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&#x27;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'>kubernetes.core.k8s</span> module.</div> <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -1,660 +0,0 @@
.. _kubernetes.core.k8s_taint_module:
*************************
kubernetes.core.k8s_taint
*************************
**Taint a node in a Kubernetes/OpenShift cluster**
Version added: 2.3.0
.. contents::
:local:
:depth: 1
Synopsis
--------
- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
- Untaint will remove taints from nodes as needed.
Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.6
- kubernetes >= 12.0.0
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="2">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>api_key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>ca_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>client_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>client_key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>context</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>host</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: &#x27;Group1,Group2&#x27;</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 &gt;= 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&#x27;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&gt;=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 &quot;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&quot;</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>&nbsp;&larr;</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>&nbsp;&larr;</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&#x27;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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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)

View File

@@ -354,7 +354,7 @@ Status
Authors Authors
~~~~~~~ ~~~~~~~
- xuxinkun (@xuxinkun) - xuxinkun
.. hint:: .. hint::

View File

@@ -1,172 +0,0 @@
.. _kubernetes.core.kustomize_lookup:
*************************
kubernetes.core.kustomize
*************************
**Build a set of kubernetes resources using a 'kustomization.yaml' file.**
Version added: 2.2.0
.. contents::
:local:
:depth: 1
Synopsis
--------
- Uses the kustomize or the kubectl tool.
- Return the result of ``kustomize build`` or ``kubectl kustomize``.
Requirements
------------
The below requirements are needed on the local Ansible controller node that executes this lookup.
- python >= 3.6
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th>Configuration</th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>binary_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The path of a kustomize or kubectl binary to use.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>dir</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">"."</div>
</td>
<td>
</td>
<td>
<div>The directory path containing &#x27;kustomization.yaml&#x27;, or a git repository URL with a path suffix specifying same with respect to the repository root.</div>
<div>If omitted, &#x27;.&#x27; is assumed.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>opt_dirs</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>An optional list of directories to search for the executable in addition to PATH.</div>
</td>
</tr>
</table>
<br/>
Notes
-----
.. note::
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
Examples
--------
.. code-block:: yaml
- name: Run lookup using kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
- name: Run lookup using kubectl kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
- name: Create kubernetes resources for lookup output
k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
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 lookup:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>_list</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></td>
<td>
<div>YAML string for the object definitions returned from the tool execution.</div>
<br/>
<div style="font-size: smaller"><b>Sample:</b></div>
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">{&#x27;kind&#x27;: &#x27;ConfigMap&#x27;, &#x27;apiVersion&#x27;: &#x27;v1&#x27;, &#x27;metadata&#x27;: {&#x27;name&#x27;: &#x27;my-config-map&#x27;, &#x27;namespace&#x27;: &#x27;default&#x27;}, &#x27;data&#x27;: {&#x27;key1&#x27;: &#x27;val1&#x27;}}</div>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)
.. hint::
Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up.

View File

@@ -25,7 +25,7 @@ tags:
- openshift - openshift
- okd - okd
- cluster - cluster
version: 2.3.0 version: 2.1.1
build_ignore: build_ignore:
- .DS_Store - .DS_Store
- '*.tar.gz' - '*.tar.gz'

View File

@@ -9,14 +9,40 @@ action_groups:
k8s: k8s:
- k8s - k8s
- k8s_exec - k8s_exec
- k8s_facts
- k8s_info - k8s_info
- k8s_log - k8s_log
- k8s_scale - k8s_scale
- k8s_service - k8s_service
- k8s_cp
- 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_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

View File

@@ -1 +0,0 @@
k8s_info.py

View File

@@ -1 +0,0 @@
k8s_info.py

View File

@@ -3,23 +3,16 @@
# Copyright (c) 2020, Ansible Project # Copyright (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import copy import copy
import traceback import traceback
import os
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 ( from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
AnsibleError,
AnsibleFileNotFound,
AnsibleAction,
AnsibleActionFail,
)
from ansible.module_utils.parsing.convert_bool import boolean from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import string_types, iteritems from ansible.module_utils.six import string_types, iteritems
from ansible.module_utils._text import to_text, to_bytes, to_native from ansible.module_utils._text import to_text, to_bytes, to_native
@@ -34,19 +27,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))
@@ -54,19 +47,15 @@ class ActionModule(ActionBase):
try: try:
tmp_source = self._loader.get_real_file(source) tmp_source = self._loader.get_real_file(source)
except AnsibleFileNotFound as e: except AnsibleFileNotFound as e:
raise AnsibleActionFail( raise AnsibleActionFail("could not find template=%s, %s" % (source, to_text(e)))
"could not find template=%s, %s" % (source, to_text(e)) b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict')
)
b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
try: try:
with open(b_tmp_source, "rb") as f: with open(b_tmp_source, 'rb') as f:
try: try:
template_data = to_text(f.read(), errors="surrogate_or_strict") template_data = to_text(f.read(), errors='surrogate_or_strict')
except UnicodeError: except UnicodeError:
raise AnsibleActionFail( raise AnsibleActionFail("Template source files must be utf-8 encoded")
"Template source files must be utf-8 encoded"
)
yield template_data yield template_data
except AnsibleAction: except AnsibleAction:
raise raise
@@ -83,102 +72,62 @@ class ActionModule(ActionBase):
"block_start_string": None, "block_start_string": None,
"block_end_string": None, "block_end_string": None,
"trim_blocks": True, "trim_blocks": True,
"lstrip_blocks": False, "lstrip_blocks": False
} }
if isinstance(template, string_types): if isinstance(template, string_types):
# treat this as raw_params # treat this as raw_params
template_param["path"] = template template_param['path'] = template
elif isinstance(template, dict): elif isinstance(template, dict):
template_args = template template_args = template
template_path = template_args.get("path", None) template_path = template_args.get('path', None)
if not template_path: if not template_path:
raise AnsibleActionFail("Please specify path for template.") raise AnsibleActionFail("Please specify path for template.")
template_param["path"] = template_path template_param['path'] = template_path
# Options type validation strings # Options type validation strings
for s_type in ( for s_type in ('newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
"newline_sequence", 'block_end_string'):
"variable_start_string",
"variable_end_string",
"block_start_string",
"block_end_string",
):
if s_type in template_args: if s_type in template_args:
value = ensure_type(template_args[s_type], "string") value = ensure_type(template_args[s_type], 'string')
if value is not None and not isinstance(value, string_types): if value is not None and not isinstance(value, string_types):
raise AnsibleActionFail( raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
"%s is expected to be a string, but got %s instead"
% (s_type, type(value))
)
try: try:
template_param.update( template_param.update({
{ "trim_blocks": boolean(template_args.get('trim_blocks', True), strict=False),
"trim_blocks": boolean( "lstrip_blocks": boolean(template_args.get('lstrip_blocks', False), strict=False)
template_args.get("trim_blocks", True), strict=False })
),
"lstrip_blocks": boolean(
template_args.get("lstrip_blocks", False), strict=False
),
}
)
except TypeError as e: except TypeError as e:
raise AnsibleActionFail(to_native(e)) raise AnsibleActionFail(to_native(e))
template_param.update( template_param.update({
{ "newline_sequence": template_args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE),
"newline_sequence": template_args.get( "variable_start_string": template_args.get('variable_start_string', None),
"newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE "variable_end_string": template_args.get('variable_end_string', None),
), "block_start_string": template_args.get('block_start_string', None),
"variable_start_string": template_args.get( "block_end_string": template_args.get('block_end_string', None)
"variable_start_string", None })
),
"variable_end_string": template_args.get(
"variable_end_string", None
),
"block_start_string": template_args.get("block_start_string", None),
"block_end_string": template_args.get("block_end_string", None),
}
)
else: else:
raise AnsibleActionFail( raise AnsibleActionFail("Error while reading template file - "
"Error while reading template file - " "a string or dict for template expected, but got %s instead" % type(template))
"a string or dict for template expected, but got %s instead"
% type(template)
)
return template_param return template_param
def import_jinja2_lstrip(self, templates): def import_jinja2_lstrip(self, templates):
# Option `lstrip_blocks' was added in Jinja2 version 2.7. # Option `lstrip_blocks' was added in Jinja2 version 2.7.
if any(tmp["lstrip_blocks"] for tmp in templates): if any([tmp['lstrip_blocks'] for tmp in templates]):
try: try:
import jinja2.defaults import jinja2.defaults
except ImportError: except ImportError:
raise AnsibleError( raise AnsibleError('Unable to import Jinja2 defaults for determining Jinja2 features.')
"Unable to import Jinja2 defaults for determining Jinja2 features."
)
try: try:
jinja2.defaults.LSTRIP_BLOCKS jinja2.defaults.LSTRIP_BLOCKS
except AttributeError: except AttributeError:
raise AnsibleError( raise AnsibleError("Option `lstrip_blocks' is only available in Jinja2 versions >=2.7")
"Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
)
def load_template(self, template, new_module_args, task_vars): def load_template(self, template, new_module_args, task_vars):
# template is only supported by k8s module. # template is only supported by k8s module.
if self._task.action not in ( if self._task.action not in ('k8s', 'kubernetes.core.k8s', 'community.okd.k8s'):
"k8s", raise AnsibleActionFail("'template' is only supported parameter for 'k8s' module.")
"kubernetes.core.k8s",
"community.okd.k8s",
"redhat.openshift.k8s",
"community.kubernetes.k8s",
"openshift_adm_groups_sync",
"community.okd.openshift_adm_groups_sync",
"redhat.openshift.openshift_adm_groups_sync",
):
raise AnsibleActionFail(
"'template' is only a supported parameter for the 'k8s' module."
)
template_params = [] template_params = []
if isinstance(template, string_types) or isinstance(template, dict): if isinstance(template, string_types) or isinstance(template, dict):
@@ -187,11 +136,8 @@ class ActionModule(ActionBase):
for element in template: for element in template:
template_params.append(self.get_template_args(element)) template_params.append(self.get_template_args(element))
else: else:
raise AnsibleActionFail( raise AnsibleActionFail("Error while reading template file - "
"Error while reading template file - " "a string or dict for template expected, but got %s instead" % type(template))
"a string or dict for template expected, but got %s instead"
% type(template)
)
self.import_jinja2_lstrip(template_params) self.import_jinja2_lstrip(template_params)
@@ -202,31 +148,20 @@ class ActionModule(ActionBase):
old_vars = self._templar.available_variables old_vars = self._templar.available_variables
default_environment = {} default_environment = {}
for key in ( for key in ("newline_sequence", "variable_start_string", "variable_end_string",
"newline_sequence", "block_start_string", "block_end_string", "trim_blocks", "lstrip_blocks"):
"variable_start_string",
"variable_end_string",
"block_start_string",
"block_end_string",
"trim_blocks",
"lstrip_blocks",
):
if hasattr(self._templar.environment, key): if hasattr(self._templar.environment, key):
default_environment[key] = getattr(self._templar.environment, key) default_environment[key] = getattr(self._templar.environment, key)
for template_item in template_params: for template_item in template_params:
# We need to convert unescaped sequences to proper escaped sequences for Jinja2 # We need to convert unescaped sequences to proper escaped sequences for Jinja2
newline_sequence = template_item["newline_sequence"] newline_sequence = template_item['newline_sequence']
if newline_sequence in wrong_sequences: if newline_sequence in wrong_sequences:
template_item["newline_sequence"] = allowed_sequences[ template_item['newline_sequence'] = allowed_sequences[wrong_sequences.index(newline_sequence)]
wrong_sequences.index(newline_sequence)
]
elif newline_sequence not in allowed_sequences: elif newline_sequence not in allowed_sequences:
raise AnsibleActionFail( raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")
"newline_sequence needs to be one of: \n, \r or \r\n"
)
# template the source data locally & get ready to transfer # template the source data locally & get ready to transfer
with self.get_template_data(template_item["path"]) as template_data: with self.get_template_data(template_item['path']) as template_data:
# add ansible 'template' vars # add ansible 'template' vars
temp_vars = copy.deepcopy(task_vars) temp_vars = copy.deepcopy(task_vars)
for key, value in iteritems(template_item): for key, value in iteritems(template_item):
@@ -234,68 +169,18 @@ class ActionModule(ActionBase):
if value is not None: if value is not None:
setattr(self._templar.environment, key, value) setattr(self._templar.environment, key, value)
else: else:
setattr( setattr(self._templar.environment, key, default_environment.get(key))
self._templar.environment,
key,
default_environment.get(key),
)
self._templar.available_variables = temp_vars self._templar.available_variables = temp_vars
result = self._templar.do_template( result = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
template_data,
preserve_trailing_newlines=True,
escape_backslashes=False,
)
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):
# 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",
):
raise AnsibleActionFail(
"'local_path' is only supported parameter for 'k8s_cp' module."
)
if os.path.exists(local_path):
return local_path
try:
# find in expected paths
return self._find_needle("files", local_path)
except AnsibleError:
raise AnsibleActionFail(
"%s does not exist in local filesystem" % local_path
)
def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
if isinstance(kubeconfig, string_types):
# find the kubeconfig in the expected search path
if not remote_transport:
# kubeconfig is local
# find in expected paths
kubeconfig = self._find_needle("files", kubeconfig)
# decrypt kubeconfig found
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
new_module_args["kubeconfig"] = actual_file
elif isinstance(kubeconfig, dict):
new_module_args["kubeconfig"] = kubeconfig
else:
raise AnsibleActionFail(
"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()
@@ -306,61 +191,55 @@ 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: # find the kubeconfig in the expected search path
if kubeconfig and not remote_transport:
# kubeconfig is local
try: try:
self.get_kubeconfig(kubeconfig, remote_transport, new_module_args) # find in expected paths
kubeconfig = self._find_needle('files', kubeconfig)
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
# decrypt kubeconfig found
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
new_module_args['kubeconfig'] = actual_file
# 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( result.update(self._execute_module(module_name=self._task.action, task_vars=task_vars))
self._execute_module(
module_name=self._task.action, task_vars=task_vars
)
)
return self._ensure_invocation(result) return self._ensure_invocation(result)
# src is local # src is local
try: try:
# find in expected paths # find in expected paths
src = self._find_needle("files", src) src = self._find_needle('files', src)
except AnsibleError as e: except AnsibleError as e:
result["failed"] = True result['failed'] = True
result["msg"] = to_text(e) result['msg'] = to_text(e)
result["exception"] = traceback.format_exc() result['exception'] = traceback.format_exc()
return result return result
if src: if src:
new_module_args["src"] = src new_module_args['src'] = src
template = self._task.args.get("template", None) template = self._task.args.get('template', None)
if template: if template:
self.load_template(template, new_module_args, task_vars) self.load_template(template, new_module_args, task_vars)
local_path = self._task.args.get("local_path")
state = self._task.args.get("state", None)
if local_path and state == "to_pod":
new_module_args["local_path"] = self.get_file_realpath(local_path)
# Execute the k8s_* module. # Execute the k8s_* module.
module_return = self._execute_module( module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
module_name=self._task.action,
module_args=new_module_args,
task_vars=task_vars,
)
# Delete tmp path # Delete tmp path
self._remove_tmp_path(self._connection._shell.tmpdir) self._remove_tmp_path(self._connection._shell.tmpdir)

View File

@@ -17,15 +17,14 @@
# #
# 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
name: kubectl connection: kubectl
short_description: Execute tasks in pods running on Kubernetes. short_description: Execute tasks in pods running on Kubernetes.
@@ -186,26 +185,26 @@ from ansible.utils.display import Display
display = Display() display = Display()
CONNECTION_TRANSPORT = "kubectl" CONNECTION_TRANSPORT = 'kubectl'
CONNECTION_OPTIONS = { CONNECTION_OPTIONS = {
"kubectl_container": "-c", 'kubectl_container': '-c',
"kubectl_namespace": "-n", 'kubectl_namespace': '-n',
"kubectl_kubeconfig": "--kubeconfig", 'kubectl_kubeconfig': '--kubeconfig',
"kubectl_context": "--context", 'kubectl_context': '--context',
"kubectl_host": "--server", 'kubectl_host': '--server',
"kubectl_username": "--username", 'kubectl_username': '--username',
"kubectl_password": "--password", 'kubectl_password': '--password',
"client_cert": "--client-certificate", 'client_cert': '--client-certificate',
"client_key": "--client-key", 'client_key': '--client-key',
"ca_cert": "--certificate-authority", 'ca_cert': '--certificate-authority',
"validate_certs": "--insecure-skip-tls-verify", 'validate_certs': '--insecure-skip-tls-verify',
"kubectl_token": "--token", 'kubectl_token': '--token'
} }
class Connection(ConnectionBase): class Connection(ConnectionBase):
""" Local kubectl based connections """ ''' Local kubectl based connections '''
transport = CONNECTION_TRANSPORT transport = CONNECTION_TRANSPORT
connection_options = CONNECTION_OPTIONS connection_options = CONNECTION_OPTIONS
@@ -218,70 +217,55 @@ 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: if cmd_arg in kwargs:
self.transport_cmd = kwargs[cmd_arg] self.transport_cmd = kwargs[cmd_arg]
else: else:
self.transport_cmd = distutils.spawn.find_executable(self.transport) self.transport_cmd = distutils.spawn.find_executable(self.transport)
if not self.transport_cmd: if not self.transport_cmd:
raise AnsibleError( raise AnsibleError("{0} command not found in PATH".format(self.transport))
"{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( local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
u"{0}={1}".format( censored_local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
self.connection_options[key], str(skip_verify_ssl).lower() elif not key.endswith('container') and self.get_option(key) and self.connection_options.get(key):
)
)
censored_local_cmd.append(
u"{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:
censored_local_cmd += [cmd_arg, self.get_option(key)]
extra_args_name = u"{0}_extra_args".format(self.transport) extra_args_name = u'{0}_extra_args'.format(self.transport)
if self.get_option(extra_args_name): if self.get_option(extra_args_name):
local_cmd += self.get_option(extra_args_name).split(" ") local_cmd += self.get_option(extra_args_name).split(' ')
censored_local_cmd += self.get_option(extra_args_name).split(" ") censored_local_cmd += self.get_option(extra_args_name).split(' ')
pod = self.get_option(u"{0}_pod".format(self.transport)) pod = self.get_option(u'{0}_pod'.format(self.transport))
if not pod: if not pod:
pod = self._play_context.remote_addr pod = self._play_context.remote_addr
# -i is needed to keep stdin open which allows pipelining to work # -i is needed to keep stdin open which allows pipelining to work
local_cmd += ["exec", "-i", pod] local_cmd += ['exec', '-i', pod]
censored_local_cmd += ["exec", "-i", pod] censored_local_cmd += ['exec', '-i', pod]
# if the pod has more than one container, then container is required # if the pod has more than one container, then container is required
container_arg_name = u"{0}_container".format(self.transport) container_arg_name = u'{0}_container'.format(self.transport)
if self.get_option(container_arg_name): if self.get_option(container_arg_name):
local_cmd += ["-c", self.get_option(container_arg_name)] local_cmd += ['-c', self.get_option(container_arg_name)]
censored_local_cmd += ["-c", self.get_option(container_arg_name)] censored_local_cmd += ['-c', self.get_option(container_arg_name)]
local_cmd += ["--"] + cmd local_cmd += ['--'] + cmd
censored_local_cmd += ["--"] + cmd censored_local_cmd += ['--'] + cmd
return local_cmd, censored_local_cmd return local_cmd, censored_local_cmd
@@ -289,37 +273,25 @@ 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( display.vvv(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._play_context.remote_addr)
u"ESTABLISH {0} CONNECTION".format(self.transport),
host=self._play_context.remote_addr,
)
self._connected = True self._connected = True
def exec_command(self, cmd, in_data=None, sudoable=False): def exec_command(self, cmd, in_data=None, sudoable=False):
""" Run a command in the container """ """ Run a command in the container """
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
local_cmd, censored_local_cmd = self._build_exec_cmd( local_cmd, censored_local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
[self._play_context.executable, "-c", cmd]
)
display.vvv( display.vvv("EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr)
"EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
) p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd] stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
local_cmd,
shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = p.communicate(in_data) stdout, stderr = p.communicate(in_data)
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
@@ -327,7 +299,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)
@@ -335,89 +307,61 @@ 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( display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
"PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
)
out_path = self._prefix_login_path(out_path) out_path = self._prefix_login_path(out_path)
if not os.path.exists(to_bytes(in_path, errors="surrogate_or_strict")): if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path) raise AnsibleFileNotFound(
"file or module does not exist: %s" % in_path)
out_path = shlex_quote(out_path) out_path = shlex_quote(out_path)
# kubectl doesn't have native support for copying files into # kubectl doesn't have native support for copying files into
# running containers, so we use kubectl exec to implement this # running containers, so we use kubectl exec to implement this
with open(to_bytes(in_path, errors="surrogate_or_strict"), "rb") as in_file: with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
if not os.fstat(in_file.fileno()).st_size: if not os.fstat(in_file.fileno()).st_size:
count = " count=0" count = ' count=0'
else: else:
count = "" count = ''
args, dummy = self._build_exec_cmd( args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
[ args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
self._play_context.executable,
"-c",
"dd of=%s bs=%s%s && sleep 0" % (out_path, BUFSIZE, count),
]
)
args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
try: try:
p = subprocess.Popen( p = subprocess.Popen(args, stdin=in_file,
args, stdin=in_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE stdout=subprocess.PIPE, stderr=subprocess.PIPE)
)
except OSError: except OSError:
raise AnsibleError( raise AnsibleError("kubectl connection requires dd command in the container to put files")
"kubectl connection requires dd command in the container to put files"
)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
if p.returncode != 0: if p.returncode != 0:
raise AnsibleError( raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
"failed to transfer file %s to %s:\n%s\n%s"
% (in_path, out_path, stdout, stderr)
)
def fetch_file(self, in_path, out_path): def fetch_file(self, in_path, out_path):
""" Fetch a file from container to local. """ """ Fetch a file from container to local. """
super(Connection, self).fetch_file(in_path, out_path) super(Connection, self).fetch_file(in_path, out_path)
display.vvv( display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
"FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
)
in_path = self._prefix_login_path(in_path) in_path = self._prefix_login_path(in_path)
out_dir = os.path.dirname(out_path) out_dir = os.path.dirname(out_path)
# kubectl doesn't have native support for fetching files from # kubectl doesn't have native support for fetching files from
# running containers, so we use kubectl exec to implement this # running containers, so we use kubectl exec to implement this
args, dummy = self._build_exec_cmd( args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
[self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)] args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
)
args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
actual_out_path = os.path.join(out_dir, os.path.basename(in_path)) actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
with open( with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
to_bytes(actual_out_path, errors="surrogate_or_strict"), "wb"
) as out_file:
try: try:
p = subprocess.Popen( p = subprocess.Popen(args, stdin=subprocess.PIPE,
args, stdin=subprocess.PIPE, stdout=out_file, stderr=subprocess.PIPE stdout=out_file, stderr=subprocess.PIPE)
)
except OSError: except OSError:
raise AnsibleError( raise AnsibleError(
"{0} connection requires dd command in the container to fetch files".format( "{0} connection requires dd command in the container to fetch files".format(self.transport)
self.transport
)
) )
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
if p.returncode != 0: if p.returncode != 0:
raise AnsibleError( raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
"failed to fetch file %s to %s:\n%s\n%s"
% (in_path, out_path, stdout, stderr)
)
if actual_out_path != out_path: if actual_out_path != out_path:
os.rename( os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
to_bytes(actual_out_path, errors="strict"),
to_bytes(out_path, errors="strict"),
)
def close(self): def close(self):
""" Terminate the connection. Nothing to do for kubectl""" """ Terminate the connection. Nothing to do for kubectl"""

View File

@@ -6,14 +6,13 @@
# Options for common Helm modules # Options for common Helm modules
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
binary_path: binary_path:
description: description:
@@ -57,4 +56,4 @@ options:
type: path type: path
aliases: [ ssl_ca_cert ] aliases: [ ssl_ca_cert ]
version_added: "1.2.0" version_added: "1.2.0"
""" '''

View File

@@ -5,14 +5,13 @@
# Options for authenticating with the API. # Options for authenticating with the API.
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
host: host:
description: description:
@@ -28,8 +27,7 @@ options:
options are provided, the Kubernetes client will attempt to load the default options are provided, the Kubernetes client will attempt to load the default
configuration file from I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment configuration file from I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment
variable. variable.
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0. type: path
type: raw
context: context:
description: description:
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable. - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.
@@ -77,14 +75,6 @@ options:
- The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable. - The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY). - Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
type: str type: str
no_proxy:
description:
- The comma separated list of hosts/domains/IP/CIDR that shouldn't go through proxy. Can also be specified via K8S_AUTH_NO_PROXY environment variable.
- Please note that this module does not pick up typical proxy settings from the environment (e.g. NO_PROXY).
- This feature requires kubernetes>=19.15.0. When kubernetes library is less than 19.15.0, it fails even no_proxy set in correct.
- example value is "localhost,.local,.example.com,127.0.0.1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
type: str
version_added: 2.3.0
proxy_headers: proxy_headers:
description: description:
- The Header used for the HTTP proxy. - The Header used for the HTTP proxy.
@@ -119,21 +109,8 @@ options:
- Please note that the current version of the k8s python client library does not support setting this flag to True yet. - Please note that the current version of the k8s python client library does not support setting this flag to True yet.
- "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169" - "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169"
type: bool type: bool
impersonate_user:
description:
- Username to impersonate for the operation.
- Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.
type: str
version_added: 2.3.0
impersonate_groups:
description:
- Group(s) to impersonate for the operation.
- "Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: 'Group1,Group2'"
type: list
elements: str
version_added: 2.3.0
notes: notes:
- "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full - "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full
certificate chain for the API server must be provided via C(ca_cert) or in the certificate chain for the API server must be provided via C(ca_cert) or in the
kubeconfig file." kubeconfig file."
""" '''

View File

@@ -5,14 +5,13 @@
# Options for specifying object wait # Options for specifying object wait
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
delete_options: delete_options:
type: dict type: dict
@@ -49,4 +48,4 @@ options:
type: str type: str
description: description:
- Specify the UID of the target object. - Specify the UID of the target object.
""" '''

View File

@@ -5,14 +5,13 @@
# Options for selecting or identifying a specific K8s object # Options for selecting or identifying a specific K8s object
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
api_version: api_version:
description: description:
@@ -50,4 +49,4 @@ options:
- If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition) - If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
will override this option. will override this option.
type: str type: str
""" '''

View File

@@ -5,14 +5,13 @@
# Options for providing an object configuration # Options for providing an object configuration
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
resource_definition: resource_definition:
description: description:
@@ -29,6 +28,6 @@ options:
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup - Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
I(resource_definition). See Examples below. I(resource_definition). See Examples below.
- Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module. - Mutually exclusive with I(template) in case of M(k8s) module.
type: path type: path
""" '''

View File

@@ -5,14 +5,13 @@
# Options used by scale modules. # Options used by scale modules.
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
replicas: replicas:
description: description:
@@ -47,4 +46,4 @@ options:
default: 5 default: 5
type: int type: int
version_added: 2.0.0 version_added: 2.0.0
""" '''

View File

@@ -5,14 +5,13 @@
# Options for specifying object state # Options for specifying object state
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
state: state:
description: description:
@@ -28,4 +27,4 @@ options:
- If set to C(yes), and I(state) is C(present), an existing object will be replaced. - If set to C(yes), and I(state) is C(present), an existing object will be replaced.
type: bool type: bool
default: no default: no
""" '''

View File

@@ -5,14 +5,13 @@
# Options for specifying object wait # Options for specifying object wait
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
class ModuleDocFragment(object): class ModuleDocFragment(object):
DOCUMENTATION = r""" DOCUMENTATION = r'''
options: options:
wait: wait:
description: description:
@@ -65,4 +64,4 @@ options:
- The possible reasons in a condition are specific to each resource type in Kubernetes. - The possible reasons in a condition are specific to each resource type in Kubernetes.
- See the API documentation of the status field for a given resource to see possible choices. - See the API documentation of the status field for a given resource to see possible choices.
type: dict type: dict
""" '''

View File

@@ -2,15 +2,12 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.errors import AnsibleFilterError from ansible.errors import AnsibleFilterError
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import ( from ansible_collections.kubernetes.core.plugins.module_utils.hashes import generate_hash
generate_hash,
)
def k8s_config_resource_name(resource): def k8s_config_resource_name(resource):
@@ -18,14 +15,15 @@ def k8s_config_resource_name(resource):
Generate resource name for the given resource of type ConfigMap, Secret Generate resource name for the given resource of type ConfigMap, Secret
""" """
try: try:
return resource["metadata"]["name"] + "-" + generate_hash(resource) return resource['metadata']['name'] + '-' + generate_hash(resource)
except KeyError: except KeyError:
raise AnsibleFilterError( raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name")
"resource must have a metadata.name key to generate a resource name"
)
# ---- Ansible filters ---- # ---- Ansible filters ----
class FilterModule(object): class FilterModule(object):
def filters(self): def filters(self):
return {"k8s_config_resource_name": k8s_config_resource_name} return {
'k8s_config_resource_name': k8s_config_resource_name
}

View File

@@ -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,17 +112,12 @@ plugin: kubernetes.core.k8s
connections: connections:
- kubeconfig: /path/to/config - kubeconfig: /path/to/config
context: 'awx/192-168-64-4:8443/developer' context: 'awx/192-168-64-4:8443/developer'
""" '''
import json import json
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible_collections.kubernetes.core.plugins.module_utils.common import ( from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception, get_api_client
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:
@@ -133,13 +128,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):
@@ -147,10 +142,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)
@@ -159,13 +154,11 @@ 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( "This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
k8s_import_exception
)
) )
source_data = None source_data = None
@@ -186,15 +179,11 @@ 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( raise K8sInventoryException("Expecting connection to be a dictionary.")
"Expecting connection to be a dictionary."
)
client = get_api_client(**connection) client = get_api_client(**connection)
name = connection.get( name = connection.get('name', self.get_default_host_name(client.configuration.host))
"name", self.get_default_host_name(client.configuration.host) if connection.get('namespaces'):
) namespaces = connection['namespaces']
if connection.get("namespaces"):
namespaces = connection["namespaces"]
else: else:
namespaces = self.get_available_namespaces(client) namespaces = self.get_available_namespaces(client)
for namespace in namespaces: for namespace in namespaces:
@@ -210,36 +199,27 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
@staticmethod @staticmethod
def get_default_host_name(host): def get_default_host_name(host):
return ( return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_')
host.replace("https://", "")
.replace("http://", "")
.replace(".", "-")
.replace(":", "_")
)
def get_available_namespaces(self, client): def get_available_namespaces(self, client):
v1_namespace = client.resources.get(api_version="v1", kind="Namespace") v1_namespace = client.resources.get(api_version='v1', kind='Namespace')
try: try:
obj = v1_namespace.get() obj = v1_namespace.get()
except DynamicApiError as exc: except DynamicApiError as exc:
self.display.debug(exc) self.display.debug(exc)
raise K8sInventoryException( raise K8sInventoryException('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc))
"Error fetching Namespace list: %s" % format_dynamic_api_exc(exc)
)
return [namespace.metadata.name for namespace in obj.items] return [namespace.metadata.name for namespace in obj.items]
def get_pods_for_namespace(self, client, name, namespace): def get_pods_for_namespace(self, client, name, namespace):
v1_pod = client.resources.get(api_version="v1", kind="Pod") v1_pod = client.resources.get(api_version='v1', kind='Pod')
try: try:
obj = v1_pod.get(namespace=namespace) obj = v1_pod.get(namespace=namespace)
except DynamicApiError as exc: except DynamicApiError as exc:
self.display.debug(exc) self.display.debug(exc)
raise K8sInventoryException( raise K8sInventoryException('Error fetching Pod list: %s' % format_dynamic_api_exc(exc))
"Error fetching Pod list: %s" % format_dynamic_api_exc(exc)
)
namespace_group = "namespace_{0}".format(namespace) namespace_group = 'namespace_{0}'.format(namespace)
namespace_pods_group = "{0}_pods".format(namespace_group) namespace_pods_group = '{0}_pods'.format(namespace_group)
self.inventory.add_group(name) self.inventory.add_group(name)
self.inventory.add_group(namespace_group) self.inventory.add_group(namespace_group)
@@ -250,14 +230,12 @@ 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 = ( pod_annotations = {} if not pod.metadata.annotations else dict(pod.metadata.annotations)
{} if not pod.metadata.annotations else dict(pod.metadata.annotations)
)
if pod.metadata.labels: if pod.metadata.labels:
# create a group for each label_value # create a group for each label_value
for key, value in pod.metadata.labels: for key, value in pod.metadata.labels:
group_name = "label_{0}_{1}".format(key, value) group_name = 'label_{0}_{1}'.format(key, value)
if group_name not in pod_groups: if group_name not in pod_groups:
pod_groups.append(group_name) pod_groups.append(group_name)
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
@@ -270,7 +248,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:
@@ -278,85 +256,46 @@ 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( self.inventory.set_variable(container_name, 'annotations', pod_annotations)
container_name, "annotations", pod_annotations self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName)
) self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName)
self.inventory.set_variable( self.inventory.set_variable(container_name, 'pod_name', pod.spec.name)
container_name, "cluster_name", pod.metadata.clusterName self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP)
) self.inventory.set_variable(container_name, 'pod_phase', pod.status.phase)
self.inventory.set_variable( self.inventory.set_variable(container_name, 'pod_ip', pod.status.podIP)
container_name, "pod_node_name", pod.spec.nodeName self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink)
) self.inventory.set_variable(container_name, 'pod_resource_version', pod.metadata.resourceVersion)
self.inventory.set_variable(container_name, "pod_name", pod.spec.name) self.inventory.set_variable(container_name, 'pod_uid', pod.metadata.uid)
self.inventory.set_variable( self.inventory.set_variable(container_name, 'container_name', container.image)
container_name, "pod_host_ip", pod.status.hostIP self.inventory.set_variable(container_name, 'container_image', container.image)
)
self.inventory.set_variable(
container_name, "pod_phase", pod.status.phase
)
self.inventory.set_variable(container_name, "pod_ip", pod.status.podIP)
self.inventory.set_variable(
container_name, "pod_self_link", pod.metadata.selfLink
)
self.inventory.set_variable(
container_name, "pod_resource_version", pod.metadata.resourceVersion
)
self.inventory.set_variable(container_name, "pod_uid", pod.metadata.uid)
self.inventory.set_variable(
container_name, "container_name", container.image
)
self.inventory.set_variable(
container_name, "container_image", container.image
)
if container.state.running: if container.state.running:
self.inventory.set_variable( self.inventory.set_variable(container_name, 'container_state', 'Running')
container_name, "container_state", "Running"
)
if container.state.terminated: if container.state.terminated:
self.inventory.set_variable( self.inventory.set_variable(container_name, 'container_state', 'Terminated')
container_name, "container_state", "Terminated"
)
if container.state.waiting: if container.state.waiting:
self.inventory.set_variable( self.inventory.set_variable(container_name, 'container_state', 'Waiting')
container_name, "container_state", "Waiting" self.inventory.set_variable(container_name, 'container_ready', container.ready)
) self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/')
self.inventory.set_variable( self.inventory.set_variable(container_name, 'ansible_connection', self.connection_plugin)
container_name, "container_ready", container.ready self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport),
) pod_name)
self.inventory.set_variable( self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport),
container_name, "ansible_remote_tmp", "/tmp/" container.name)
) self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport),
self.inventory.set_variable( namespace)
container_name, "ansible_connection", self.connection_plugin
)
self.inventory.set_variable(
container_name, "ansible_{0}_pod".format(self.transport), pod_name
)
self.inventory.set_variable(
container_name,
"ansible_{0}_container".format(self.transport),
container.name,
)
self.inventory.set_variable(
container_name,
"ansible_{0}_namespace".format(self.transport),
namespace,
)
def get_services_for_namespace(self, client, name, namespace): def get_services_for_namespace(self, client, name, namespace):
v1_service = client.resources.get(api_version="v1", kind="Service") v1_service = client.resources.get(api_version='v1', kind='Service')
try: try:
obj = v1_service.get(namespace=namespace) obj = v1_service.get(namespace=namespace)
except DynamicApiError as exc: except DynamicApiError as exc:
self.display.debug(exc) self.display.debug(exc)
raise K8sInventoryException( raise K8sInventoryException('Error fetching Service list: %s' % format_dynamic_api_exc(exc))
"Error fetching Service list: %s" % format_dynamic_api_exc(exc)
)
namespace_group = "namespace_{0}".format(namespace) namespace_group = 'namespace_{0}'.format(namespace)
namespace_services_group = "{0}_services".format(namespace_group) namespace_services_group = '{0}_services'.format(namespace_group)
self.inventory.add_group(name) self.inventory.add_group(name)
self.inventory.add_group(namespace_group) self.inventory.add_group(namespace_group)
@@ -366,21 +305,15 @@ 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 = ( service_labels = {} if not service.metadata.labels else dict(service.metadata.labels)
{} if not service.metadata.labels else dict(service.metadata.labels) service_annotations = {} if not service.metadata.annotations else dict(service.metadata.annotations)
)
service_annotations = (
{}
if not service.metadata.annotations
else dict(service.metadata.annotations)
)
self.inventory.add_host(service_name) self.inventory.add_host(service_name)
if service.metadata.labels: if service.metadata.labels:
# create a group for each label_value # create a group for each label_value
for key, value in service.metadata.labels: for key, value in service.metadata.labels:
group_name = "label_{0}_{1}".format(key, value) group_name = 'label_{0}_{1}'.format(key, value)
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
self.inventory.add_child(group_name, service_name) self.inventory.add_child(group_name, service_name)
@@ -389,75 +322,42 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
except AnsibleError: except AnsibleError:
raise raise
ports = [ ports = [{'name': port.name,
{ 'port': port.port,
"name": port.name, 'protocol': port.protocol,
"port": port.port, 'targetPort': port.targetPort,
"protocol": port.protocol, 'nodePort': port.nodePort} for port in service.spec.ports or []]
"targetPort": port.targetPort,
"nodePort": port.nodePort,
}
for port in service.spec.ports or []
]
# add hostvars # add hostvars
self.inventory.set_variable(service_name, "object_type", "service") self.inventory.set_variable(service_name, 'object_type', 'service')
self.inventory.set_variable(service_name, "labels", service_labels) self.inventory.set_variable(service_name, 'labels', service_labels)
self.inventory.set_variable( self.inventory.set_variable(service_name, 'annotations', service_annotations)
service_name, "annotations", service_annotations self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName)
) self.inventory.set_variable(service_name, 'ports', ports)
self.inventory.set_variable( self.inventory.set_variable(service_name, 'type', service.spec.type)
service_name, "cluster_name", service.metadata.clusterName self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink)
) self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion)
self.inventory.set_variable(service_name, "ports", ports) self.inventory.set_variable(service_name, 'uid', service.metadata.uid)
self.inventory.set_variable(service_name, "type", service.spec.type)
self.inventory.set_variable(
service_name, "self_link", service.metadata.selfLink
)
self.inventory.set_variable(
service_name, "resource_version", service.metadata.resourceVersion
)
self.inventory.set_variable(service_name, "uid", service.metadata.uid)
if service.spec.externalTrafficPolicy: if service.spec.externalTrafficPolicy:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'external_traffic_policy',
service_name, service.spec.externalTrafficPolicy)
"external_traffic_policy",
service.spec.externalTrafficPolicy,
)
if service.spec.externalIPs: if service.spec.externalIPs:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs)
service_name, "external_ips", service.spec.externalIPs
)
if service.spec.externalName: if service.spec.externalName:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'external_name', service.spec.externalName)
service_name, "external_name", service.spec.externalName
)
if service.spec.healthCheckNodePort: if service.spec.healthCheckNodePort:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'health_check_node_port',
service_name, service.spec.healthCheckNodePort)
"health_check_node_port",
service.spec.healthCheckNodePort,
)
if service.spec.loadBalancerIP: if service.spec.loadBalancerIP:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'load_balancer_ip',
service_name, "load_balancer_ip", service.spec.loadBalancerIP service.spec.loadBalancerIP)
)
if service.spec.selector: if service.spec.selector:
self.inventory.set_variable( self.inventory.set_variable(service_name, 'selector', dict(service.spec.selector))
service_name, "selector", dict(service.spec.selector)
)
if ( if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress:
hasattr(service.status.loadBalancer, "ingress") load_balancer = [{'hostname': ingress.hostname,
and service.status.loadBalancer.ingress 'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress]
): self.inventory.set_variable(service_name, 'load_balancer', load_balancer)
load_balancer = [
{"hostname": ingress.hostname, "ip": ingress.ip}
for ingress in service.status.loadBalancer.ingress
]
self.inventory.set_variable(
service_name, "load_balancer", load_balancer
)

View File

@@ -3,28 +3,25 @@
# #
# 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 lookup: 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
namespace, or all matching objects for all namespaces, as well as information about the cluster. namespace, or all matching objects for all namespaces, as well as information about the cluster.
- Provides access the full range of K8s APIs. - Provides access the full range of K8s APIs.
- Enables authentication via config file, certificates, password or token. - Enables authentication via config file, certificates, password or token.
notes:
- While querying, please use C(query) or C(lookup) format with C(wantlist=True) to provide an easier and more
consistent interface. For more details, see
U(https://docs.ansible.com/ansible/latest/plugins/lookup.html#forcing-lookups-to-return-lists-query-and-wantlist-true).
options: options:
cluster_info: cluster_info:
description: description:
@@ -117,28 +114,28 @@ 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
set_fact: set_fact:
projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}" projects: "{{ lookup('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
- name: Fetch all deployments - name: Fetch all deployments
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
- name: Fetch all deployments in a namespace - name: Fetch all deployments in a namespace
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
- name: Fetch a specific deployment by name - name: Fetch a specific deployment by name
set_fact: set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}" deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
- name: Fetch with label selector - name: Fetch with label selector
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}" service: "{{ lookup('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
# Use parameters from a YAML config # Use parameters from a YAML config
@@ -148,61 +145,50 @@ EXAMPLES = """
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object - name: Using the config (loaded from a file in prior task), fetch the latest version of the object
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}" service: "{{ lookup('kubernetes.core.k8s', resource_definition=config) }}"
- name: Use a config from the local filesystem - name: Use a config from the local filesystem
set_fact: set_fact:
service: "{{ query('kubernetes.core.k8s', src='service.yml') }}" service: "{{ lookup('kubernetes.core.k8s', src='service.yml') }}"
""" """
RETURN = """ RETURN = """
_list: _list:
description: description:
- One ore more object definitions returned from the API. - One ore more object definitions returned from the API.
type: list type: complex
elements: dict contains:
sample: api_version:
- kind: ConfigMap description: The versioned schema of this representation of an object.
apiVersion: v1 returned: success
type: str
kind:
description: Represents the REST resource this object represents.
returned: success
type: str
metadata: metadata:
creationTimestamp: "2022-03-04T13:59:49Z" description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
name: my-config-map returned: success
namespace: default type: complex
resourceVersion: "418" spec:
uid: 5714b011-d090-4eac-8272-a0ea82ec0abd description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
data: returned: success
key1: val1 type: complex
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.module_utils.common.validation import check_type_bool from ansible.plugins.lookup import LookupBase
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,
)
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:
@@ -211,13 +197,12 @@ 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( "Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
k8s_import_exception
)
) )
self.kind = None self.kind = None
@@ -238,33 +223,31 @@ 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( self.namespace = resource_definition.get('metadata', {}).get('namespace', self.namespace)
"namespace", self.namespace
)
if not self.kind: if not self.kind:
raise AnsibleError( raise AnsibleError(
@@ -274,23 +257,17 @@ 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( k8s_obj = resource.get(name=self.name, namespace=self.namespace, label_selector=self.label_selector, field_selector=self.field_selector)
name=self.name,
namespace=self.namespace,
label_selector=self.label_selector,
field_selector=self.field_selector,
)
except NotFoundError: except NotFoundError:
return [] return []
if self.name: if self.name:
return [k8s_obj.to_dict()] return [k8s_obj.to_dict()]
return k8s_obj.to_dict().get("items") return [k8s_obj.to_dict().get('items')]
class LookupModule(LookupBase): class LookupModule(LookupBase):
def _run(self, terms, variables=None, **kwargs):
return KubernetesLookup().run(terms, variables=variables, **kwargs)
run = _run if not hasattr(LookupBase, "run_on_daemon") else LookupBase.run_on_daemon def run(self, terms, variables=None, **kwargs):
return KubernetesLookup().run(terms, variables=variables, **kwargs)

View File

@@ -1,131 +0,0 @@
#
# Copyright 2021 Red Hat | Ansible
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = """
name: kustomize
short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file.
version_added: "2.2.0"
author:
- Aubin Bikouo (@abikouo)
notes:
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
description:
- Uses the kustomize or the kubectl tool.
- Return the result of C(kustomize build) or C(kubectl kustomize).
options:
dir:
description:
- The directory path containing 'kustomization.yaml',
or a git repository URL with a path suffix specifying same with respect to the repository root.
- If omitted, '.' is assumed.
default: "."
binary_path:
description:
- The path of a kustomize or kubectl binary to use.
opt_dirs:
description:
- An optional list of directories to search for the executable in addition to PATH.
requirements:
- "python >= 3.6"
"""
EXAMPLES = """
- name: Run lookup using kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
- name: Run lookup using kubectl kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
- name: Create kubernetes resources for lookup output
k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
"""
RETURN = """
_list:
description:
- YAML string for the object definitions returned from the tool execution.
type: str
sample:
kind: ConfigMap
apiVersion: v1
metadata:
name: my-config-map
namespace: default
data:
key1: val1
"""
from ansible.errors import AnsibleLookupError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.process import get_bin_path
import subprocess
def get_binary_from_path(name, opt_dirs=None):
opt_arg = {}
try:
if opt_dirs is not None:
if not isinstance(opt_dirs, list):
opt_dirs = [opt_dirs]
opt_arg["opt_dirs"] = opt_dirs
bin_path = get_bin_path(name, **opt_arg)
return bin_path
except ValueError:
return None
def run_command(command):
cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return cmd.communicate()
class LookupModule(LookupBase):
def run(
self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs
):
executable_path = binary_path
if executable_path is None:
executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
if executable_path is None:
executable_path = get_binary_from_path(
name="kubectl", opt_dirs=opt_dirs
)
# validate that at least one tool was found
if executable_path is None:
raise AnsibleLookupError(
"Failed to find required executable 'kubectl' and 'kustomize' in paths"
)
# check input directory
kustomization_dir = dir
command = [executable_path]
if executable_path.endswith("kustomize"):
command += ["build", kustomization_dir]
elif executable_path.endswith("kubectl"):
command += ["kustomize", kustomization_dir]
else:
raise AnsibleLookupError(
"unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(
executable_path
)
)
(out, err) = run_command(command)
if err:
raise AnsibleLookupError(
"kustomize command failed with: {0}".format(err.decode("utf-8"))
)
return [out.decode("utf-8")]

View File

@@ -1,344 +0,0 @@
# 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 licenses/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

View File

@@ -1,25 +1,6 @@
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import os from ansible.module_utils.basic import AnsibleModule # noqa: F401
from ansible.module_utils.common.validation import check_type_bool
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.module_utils.turbo.module import (
AnsibleTurboModule as AnsibleModule,
) # noqa: F401
AnsibleModule.collection_name = "kubernetes.core"
except ImportError:
from ansible.module_utils.basic import AnsibleModule # noqa: F401
else:
from ansible.module_utils.basic import AnsibleModule # noqa: F401

View File

@@ -14,16 +14,13 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from collections import OrderedDict from collections import OrderedDict
import json import json
from ansible.module_utils.common.dict_transformations import dict_merge from ansible.module_utils.common.dict_transformations import dict_merge
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ( from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
ApplyException,
)
try: try:
from kubernetes.dynamic.exceptions import NotFoundError from kubernetes.dynamic.exceptions import NotFoundError
@@ -31,52 +28,50 @@ except ImportError:
pass pass
LAST_APPLIED_CONFIG_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration" LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
POD_SPEC_SUFFIXES = { POD_SPEC_SUFFIXES = {
"containers": "name", 'containers': 'name',
"initContainers": "name", 'initContainers': 'name',
"ephemeralContainers": "name", 'ephemeralContainers': 'name',
"volumes": "name", 'volumes': 'name',
"imagePullSecrets": "name", 'imagePullSecrets': 'name',
"containers.volumeMounts": "mountPath", 'containers.volumeMounts': 'mountPath',
"containers.volumeDevices": "devicePath", 'containers.volumeDevices': 'devicePath',
"containers.env": "name", 'containers.env': 'name',
"containers.ports": "containerPort", 'containers.ports': 'containerPort',
"initContainers.volumeMounts": "mountPath", 'initContainers.volumeMounts': 'mountPath',
"initContainers.volumeDevices": "devicePath", 'initContainers.volumeDevices': 'devicePath',
"initContainers.env": "name", 'initContainers.env': 'name',
"initContainers.ports": "containerPort", 'initContainers.ports': 'containerPort',
"ephemeralContainers.volumeMounts": "mountPath", 'ephemeralContainers.volumeMounts': 'mountPath',
"ephemeralContainers.volumeDevices": "devicePath", 'ephemeralContainers.volumeDevices': 'devicePath',
"ephemeralContainers.env": "name", 'ephemeralContainers.env': 'name',
"ephemeralContainers.ports": "containerPort", 'ephemeralContainers.ports': 'containerPort',
} }
POD_SPEC_PREFIXES = [ POD_SPEC_PREFIXES = [
"Pod.spec", 'Pod.spec',
"Deployment.spec.template.spec", 'Deployment.spec.template.spec',
"DaemonSet.spec.template.spec", 'DaemonSet.spec.template.spec',
"StatefulSet.spec.template.spec", 'StatefulSet.spec.template.spec',
"Job.spec.template.spec", 'Job.spec.template.spec',
"Cronjob.spec.jobTemplate.spec.template.spec", 'Cronjob.spec.jobTemplate.spec.template.spec',
] ]
# patch merge keys taken from generated.proto files under # patch merge keys taken from generated.proto files under
# staging/src/k8s.io/api in kubernetes/kubernetes # staging/src/k8s.io/api in kubernetes/kubernetes
STRATEGIC_MERGE_PATCH_KEYS = { STRATEGIC_MERGE_PATCH_KEYS = {
"Service.spec.ports": "port", 'Service.spec.ports': 'port',
"ServiceAccount.secrets": "name", 'ServiceAccount.secrets': 'name',
"ValidatingWebhookConfiguration.webhooks": "name", 'ValidatingWebhookConfiguration.webhooks': 'name',
"MutatingWebhookConfiguration.webhooks": "name", 'MutatingWebhookConfiguration.webhooks': 'name',
} }
STRATEGIC_MERGE_PATCH_KEYS.update( STRATEGIC_MERGE_PATCH_KEYS.update(
{ {"%s.%s" % (prefix, key): value
"%s.%s" % (prefix, key): value
for prefix in POD_SPEC_PREFIXES for prefix in POD_SPEC_PREFIXES
for key, value in POD_SPEC_SUFFIXES.items() for key, value in POD_SPEC_SUFFIXES.items()}
}
) )
@@ -84,28 +79,21 @@ def annotate(desired):
return dict( return dict(
metadata=dict( metadata=dict(
annotations={ annotations={
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps( LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True)
desired, separators=(",", ":"), indent=None, sort_keys=True
)
} }
) )
) )
def apply_patch(actual, desired): def apply_patch(actual, desired):
last_applied = ( last_applied = actual['metadata'].get('annotations', {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
actual["metadata"].get("annotations", {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
)
if last_applied: if last_applied:
# ensure that last_applied doesn't come back as a dict of unicode key/value pairs # ensure that last_applied doesn't come back as a dict of unicode key/value pairs
# json.loads can be used if we stop supporting python 2 # json.loads can be used if we stop supporting python 2
last_applied = json.loads(last_applied) last_applied = json.loads(last_applied)
patch = merge( patch = merge(dict_merge(last_applied, annotate(last_applied)),
dict_merge(last_applied, annotate(last_applied)), dict_merge(desired, annotate(desired)), actual)
dict_merge(desired, annotate(desired)),
actual,
)
if patch: if patch:
return actual, patch return actual, patch
else: else:
@@ -114,48 +102,24 @@ def apply_patch(actual, desired):
return actual, dict_merge(desired, annotate(desired)) return actual, dict_merge(desired, annotate(desired))
def apply_object(resource, definition, server_side=False): def apply_object(resource, definition):
try: try:
actual = resource.get( actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
)
if server_side:
return actual, None
except NotFoundError: except NotFoundError:
return None, dict_merge(definition, annotate(definition)) return None, dict_merge(definition, annotate(definition))
return apply_patch(actual.to_dict(), definition) return apply_patch(actual.to_dict(), definition)
def k8s_apply(resource, definition, **kwargs): def k8s_apply(resource, definition):
existing, desired = apply_object(resource, definition) existing, desired = apply_object(resource, definition)
server_side = kwargs.get("server_side", False)
if server_side:
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( return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
)
if existing == desired: if existing == desired:
return resource.get( return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
name=definition["metadata"]["name"], return resource.patch(body=desired,
namespace=definition["metadata"].get("namespace"), name=definition['metadata']['name'],
) namespace=definition['metadata'].get('namespace'),
return resource.patch( content_type='application/merge-patch+json')
body=desired,
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
content_type="application/merge-patch+json",
**kwargs
)
# The patch is the difference from actual to desired without deletions, plus deletions # The patch is the difference from actual to desired without deletions, plus deletions
@@ -164,7 +128,7 @@ def k8s_apply(resource, definition, **kwargs):
# deletions, and then apply delta to deletions as a patch, which should be strictly additive. # deletions, and then apply delta to deletions as a patch, which should be strictly additive.
def merge(last_applied, desired, actual, position=None): def merge(last_applied, desired, actual, position=None):
deletions = get_deletions(last_applied, desired) deletions = get_deletions(last_applied, desired)
delta = get_delta(last_applied, actual, desired, position or desired["kind"]) delta = get_delta(last_applied, actual, desired, position or desired['kind'])
return dict_merge(deletions, delta) return dict_merge(deletions, delta)
@@ -174,9 +138,7 @@ def list_to_dict(lst, key, position):
try: try:
result[item[key]] = item result[item[key]] = item
except KeyError: except KeyError:
raise ApplyException( raise ApplyException("Expected key '%s' not found in position %s" % (key, position))
"Expected key '%s' not found in position %s" % (key, position)
)
return result return result
@@ -195,12 +157,7 @@ def list_merge(last_applied, actual, desired, position):
if key not in actual_dict or key not in last_applied_dict: if key not in actual_dict or key not in last_applied_dict:
result.append(desired_dict[key]) result.append(desired_dict[key])
else: else:
patch = merge( patch = merge(last_applied_dict[key], desired_dict[key], actual_dict[key], position)
last_applied_dict[key],
desired_dict[key],
actual_dict[key],
position,
)
result.append(dict_merge(actual_dict[key], patch)) result.append(dict_merge(actual_dict[key], patch))
for key in actual_dict: for key in actual_dict:
if key not in desired_dict and key not in last_applied_dict: if key not in desired_dict and key not in last_applied_dict:
@@ -240,11 +197,11 @@ def recursive_list_diff(list1, list2, position=None):
def recursive_diff(dict1, dict2, position=None): def recursive_diff(dict1, dict2, position=None):
if not position: if not position:
if "kind" in dict1 and dict1.get("kind") == dict2.get("kind"): if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
position = dict1["kind"] position = dict1['kind']
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2) left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1) right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
for k in set(dict1.keys()) & set(dict2.keys()): for k in (set(dict1.keys()) & set(dict2.keys())):
if position: if position:
this_position = "%s.%s" % (position, k) this_position = "%s.%s" % (position, k)
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict): if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
@@ -289,15 +246,11 @@ def get_delta(last_applied, actual, desired, position=None):
if actual_value is None: if actual_value is None:
patch[k] = desired_value patch[k] = desired_value
elif isinstance(desired_value, dict): elif isinstance(desired_value, dict):
p = get_delta( p = get_delta(last_applied.get(k, {}), actual_value, desired_value, this_position)
last_applied.get(k, {}), actual_value, desired_value, this_position
)
if p: if p:
patch[k] = p patch[k] = p
elif isinstance(desired_value, list): elif isinstance(desired_value, list):
p = list_merge( p = list_merge(last_applied.get(k, []), actual_value, desired_value, this_position)
last_applied.get(k, []), actual_value, desired_value, this_position
)
if p: if p:
patch[k] = [item for item in p if item is not None] patch[k] = [item for item in p if item is not None]
elif actual_value != desired_value: elif actual_value != desired_value:

View File

@@ -1,4 +1,4 @@
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
@@ -12,87 +12,133 @@ def list_dict_str(value):
AUTH_PROXY_HEADERS_SPEC = dict( AUTH_PROXY_HEADERS_SPEC = dict(
proxy_basic_auth=dict(type="str", no_log=True), proxy_basic_auth=dict(type='str', no_log=True),
basic_auth=dict(type="str", no_log=True), basic_auth=dict(type='str', no_log=True),
user_agent=dict(type="str"), user_agent=dict(type='str')
) )
AUTH_ARG_SPEC = { AUTH_ARG_SPEC = {
"kubeconfig": {"type": "raw"}, 'kubeconfig': {
"context": {}, 'type': 'path',
"host": {}, },
"api_key": {"no_log": True}, 'context': {},
"username": {}, 'host': {},
"password": {"no_log": True}, 'api_key': {
"validate_certs": {"type": "bool", "aliases": ["verify_ssl"]}, 'no_log': True,
"ca_cert": {"type": "path", "aliases": ["ssl_ca_cert"]}, },
"client_cert": {"type": "path", "aliases": ["cert_file"]}, 'username': {},
"client_key": {"type": "path", "aliases": ["key_file"]}, 'password': {
"proxy": {"type": "str"}, 'no_log': True,
"no_proxy": {"type": "str"}, },
"proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC}, 'validate_certs': {
"persist_config": {"type": "bool"}, 'type': 'bool',
"impersonate_user": {}, 'aliases': ['verify_ssl'],
"impersonate_groups": {"type": "list", "elements": "str"}, },
'ca_cert': {
'type': 'path',
'aliases': ['ssl_ca_cert'],
},
'client_cert': {
'type': 'path',
'aliases': ['cert_file'],
},
'client_key': {
'type': 'path',
'aliases': ['key_file'],
},
'proxy': {
'type': 'str',
},
'proxy_headers': {
'type': 'dict',
'options': AUTH_PROXY_HEADERS_SPEC
},
'persist_config': {
'type': 'bool',
},
} }
WAIT_ARG_SPEC = dict( WAIT_ARG_SPEC = dict(
wait=dict(type="bool", default=False), wait=dict(type='bool', default=False),
wait_sleep=dict(type="int", default=5), wait_sleep=dict(type='int', default=5),
wait_timeout=dict(type="int", default=120), wait_timeout=dict(type='int', default=120),
wait_condition=dict( wait_condition=dict(
type="dict", type='dict',
default=None, default=None,
options=dict( options=dict(
type=dict(), type=dict(),
status=dict(default=True, choices=[True, False, "Unknown"]), status=dict(default=True, choices=[True, False, "Unknown"]),
reason=dict(), reason=dict()
), )
), )
) )
# Map kubernetes-client parameters to ansible parameters # Map kubernetes-client parameters to ansible parameters
AUTH_ARG_MAP = { AUTH_ARG_MAP = {
"kubeconfig": "kubeconfig", 'kubeconfig': 'kubeconfig',
"context": "context", 'context': 'context',
"host": "host", 'host': 'host',
"api_key": "api_key", 'api_key': 'api_key',
"username": "username", 'username': 'username',
"password": "password", 'password': 'password',
"verify_ssl": "validate_certs", 'verify_ssl': 'validate_certs',
"ssl_ca_cert": "ca_cert", 'ssl_ca_cert': 'ca_cert',
"cert_file": "client_cert", 'cert_file': 'client_cert',
"key_file": "client_key", 'key_file': 'client_key',
"proxy": "proxy", 'proxy': 'proxy',
"no_proxy": "no_proxy", 'proxy_headers': 'proxy_headers',
"proxy_headers": "proxy_headers", 'persist_config': 'persist_config',
"persist_config": "persist_config",
} }
NAME_ARG_SPEC = { NAME_ARG_SPEC = {
"kind": {}, 'kind': {},
"name": {}, 'name': {},
"namespace": {}, 'namespace': {},
"api_version": {"default": "v1", "aliases": ["api", "version"]}, 'api_version': {
'default': 'v1',
'aliases': ['api', 'version'],
},
} }
COMMON_ARG_SPEC = { COMMON_ARG_SPEC = {
"state": {"default": "present", "choices": ["present", "absent"]}, 'state': {
"force": {"type": "bool", "default": False}, 'default': 'present',
'choices': ['present', 'absent'],
},
'force': {
'type': 'bool',
'default': False,
},
} }
RESOURCE_ARG_SPEC = { RESOURCE_ARG_SPEC = {
"resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]}, 'resource_definition': {
"src": {"type": "path"}, 'type': list_dict_str,
} 'aliases': ['definition', 'inline']
},
ARG_ATTRIBUTES_BLACKLIST = ("property_path",) 'src': {
'type': 'path',
DELETE_OPTS_ARG_SPEC = {
"propagationPolicy": {"choices": ["Foreground", "Background", "Orphan"]},
"gracePeriodSeconds": {"type": "int"},
"preconditions": {
"type": "dict",
"options": {"resourceVersion": {"type": "str"}, "uid": {"type": "str"}},
}, },
} }
ARG_ATTRIBUTES_BLACKLIST = ('property_path',)
DELETE_OPTS_ARG_SPEC = {
'propagationPolicy': {
'choices': ['Foreground', 'Background', 'Orphan'],
},
'gracePeriodSeconds': {
'type': 'int',
},
'preconditions': {
'type': 'dict',
'options': {
'resourceVersion': {
'type': 'str',
},
'uid': {
'type': 'str',
}
}
}
}

View File

@@ -18,31 +18,21 @@ import os
from collections import defaultdict from collections import defaultdict
import hashlib import hashlib
import tempfile import tempfile
from functools import partial
import kubernetes.dynamic import kubernetes.dynamic
import kubernetes.dynamic.discovery import kubernetes.dynamic.discovery
from kubernetes import __version__ from kubernetes import __version__
from kubernetes.dynamic.exceptions import ( from kubernetes.dynamic.exceptions import (ResourceNotFoundError, ResourceNotUniqueError,
ResourceNotFoundError, ServiceUnavailableError)
ResourceNotUniqueError,
ServiceUnavailableError,
)
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ( from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
ResourceList,
)
class Discoverer(kubernetes.dynamic.discovery.Discoverer): class Discoverer(kubernetes.dynamic.discovery.Discoverer):
def __init__(self, client, cache_file): def __init__(self, client, cache_file):
self.client = client self.client = client
default_cache_file_name = "k8srcp-{0}.json".format( default_cache_file_name = 'k8srcp-{0}.json'.format(hashlib.sha256(self.__get_default_cache_id()).hexdigest())
hashlib.sha256(self.__get_default_cache_id()).hexdigest() self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cache_file_name)
)
self.__cache_file = cache_file or os.path.join(
tempfile.gettempdir(), default_cache_file_name
)
self.__init_cache() self.__init_cache()
def __get_default_cache_id(self): def __get_default_cache_id(self):
@@ -51,21 +41,21 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
cache_id = "{0}-{1}".format(self.client.configuration.host, user) cache_id = "{0}-{1}".format(self.client.configuration.host, user)
else: else:
cache_id = self.client.configuration.host cache_id = self.client.configuration.host
return cache_id.encode("utf-8") return cache_id.encode('utf-8')
def __get_user(self): def __get_user(self):
# This is intended to provide a portable method for getting a username. # This is intended to provide a portable method for getting a username.
# It could, and maybe should, be replaced by getpass.getuser() but, due # It could, and maybe should, be replaced by getpass.getuser() but, due
# to a lack of portability testing the original code is being left in # to a lack of portability testing the original code is being left in
# place. # place.
if hasattr(os, "getlogin"): if hasattr(os, 'getlogin'):
try: try:
user = os.getlogin() user = os.getlogin()
if user: if user:
return str(user) return str(user)
except OSError: except OSError:
pass pass
if hasattr(os, "getuid"): if hasattr(os, 'getuid'):
try: try:
user = os.getuid() user = os.getuid()
if user: if user:
@@ -79,13 +69,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=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:
@@ -101,25 +91,21 @@ 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( resources_raw = list(filter(lambda resource: '/' not in resource['name'], resources_response))
filter(lambda resource: "/" not in resource["name"], resources_response) subresources_raw = list(filter(lambda resource: '/' in resource['name'], resources_response))
)
subresources_raw = list(
filter(lambda resource: "/" in resource["name"], resources_response)
)
for subresource in subresources_raw: for subresource in subresources_raw:
resource, name = subresource["name"].split("/") resource, name = subresource['name'].split('/')
subresources[resource][name] = subresource subresources[resource][name] = subresource
for resource in resources_raw: for resource in resources_raw:
# Prevent duplicate keys # Prevent duplicate keys
for key in ("prefix", "group", "api_version", "client", "preferred"): for key in ('prefix', 'group', 'api_version', 'client', 'preferred'):
resource.pop(key, None) resource.pop(key, None)
resourceobj = kubernetes.dynamic.Resource( resourceobj = kubernetes.dynamic.Resource(
@@ -128,25 +114,19 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
api_version=version, api_version=version,
client=self.client, client=self.client,
preferred=preferred, preferred=preferred,
subresources=subresources.get(resource["name"]), subresources=subresources.get(resource['name']),
**resource **resource
) )
resources[resource["kind"]].append(resourceobj) resources[resource['kind']].append(resourceobj)
resource_lookup = { resource_lookup = {
"prefix": prefix, 'prefix': prefix,
"group": group, 'group': group,
"api_version": version, 'api_version': version,
"kind": resourceobj.kind, 'kind': resourceobj.kind,
"name": resourceobj.name, 'name': resourceobj.name
} }
resource_list = ResourceList( resource_list = ResourceList(self.client, group=group, api_version=version, base_kind=resource['kind'], base_resource_lookup=resource_lookup)
self.client,
group=group,
api_version=version,
base_kind=resource["kind"],
base_resource_lookup=resource_lookup,
)
resources[resource_list.kind].append(resource_list) resources[resource_list.kind].append(resource_list)
return resources return resources
@@ -158,32 +138,23 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
""" """
results = self.search(**kwargs) results = self.search(**kwargs)
# If there are multiple matches, prefer exact matches on api_version # If there are multiple matches, prefer exact matches on api_version
if len(results) > 1 and kwargs.get("api_version"): if len(results) > 1 and kwargs.get('api_version'):
results = [ results = [
result result for result in results if result.group_version == kwargs['api_version']
for result in results
if result.group_version == kwargs["api_version"]
] ]
# If there are multiple matches, prefer non-List kinds # If there are multiple matches, prefer non-List kinds
if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results): if len(results) > 1 and not all([isinstance(x, ResourceList) for x in results]):
results = [ results = [result for result in results if not isinstance(result, ResourceList)]
result for result in results if not isinstance(result, ResourceList)
]
# if multiple resources are found that share a GVK, prefer the one with the most supported verbs # if multiple resources are found that share a GVK, prefer the one with the most supported verbs
if ( if len(results) > 1 and len(set((x.group_version, x.kind) for x in results)) == 1:
len(results) > 1
and len(set((x.group_version, x.kind) for x in results)) == 1
):
if len(set(len(x.verbs) for x in results)) != 1: if len(set(len(x.verbs) for x in results)) != 1:
results = [max(results, key=lambda x: len(x.verbs))] results = [max(results, key=lambda x: len(x.verbs))]
if len(results) == 1: if len(results) == 1:
return results[0] return results[0]
elif not results: elif not results:
raise ResourceNotFoundError("No matches found for {0}".format(kwargs)) raise ResourceNotFoundError('No matches found for {0}'.format(kwargs))
else: else:
raise ResourceNotUniqueError( raise ResourceNotUniqueError('Multiple matches found for {0}: {1}'.format(kwargs, results))
"Multiple matches found for {0}: {1}".format(kwargs, results)
)
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer): class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
@@ -191,10 +162,6 @@ class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
Discoverer.__init__(self, client, cache_file) Discoverer.__init__(self, client, cache_file)
self.__update_cache = False self.__update_cache = False
@property
def update_cache(self):
self.__update_cache
class CacheDecoder(json.JSONDecoder): class CacheDecoder(json.JSONDecoder):
def __init__(self, client, *args, **kwargs): def __init__(self, client, *args, **kwargs):
@@ -202,15 +169,13 @@ class CacheDecoder(json.JSONDecoder):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, obj): def object_hook(self, obj):
if "_type" not in obj: if '_type' not in obj:
return obj return obj
_type = obj.pop("_type") _type = obj.pop('_type')
if _type == "Resource": if _type == 'Resource':
return kubernetes.dynamic.Resource(client=self.client, **obj) return kubernetes.dynamic.Resource(client=self.client, **obj)
elif _type == "ResourceList": elif _type == 'ResourceList':
return ResourceList(self.client, **obj) return ResourceList(self.client, **obj)
elif _type == "ResourceGroup": elif _type == 'ResourceGroup':
return kubernetes.dynamic.discovery.ResourceGroup( return kubernetes.dynamic.discovery.ResourceGroup(obj['preferred'], resources=self.object_hook(obj['resources']))
obj["preferred"], resources=self.object_hook(obj["resources"])
)
return obj return obj

View File

@@ -14,7 +14,6 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
@@ -22,19 +21,11 @@ import kubernetes.dynamic
class ResourceList(kubernetes.dynamic.resource.ResourceList): class ResourceList(kubernetes.dynamic.resource.ResourceList):
def __init__( def __init__(self, client, group='', api_version='v1', base_kind='', kind=None, base_resource_lookup=None):
self,
client,
group="",
api_version="v1",
base_kind="",
kind=None,
base_resource_lookup=None,
):
self.client = client self.client = client
self.group = group self.group = group
self.api_version = api_version self.api_version = api_version
self.kind = kind or "{0}List".format(base_kind) self.kind = kind or '{0}List'.format(base_kind)
self.base_kind = base_kind self.base_kind = base_kind
self.base_resource_lookup = base_resource_lookup self.base_resource_lookup = base_resource_lookup
self.__base_resource = None self.__base_resource = None
@@ -43,18 +34,16 @@ class ResourceList(kubernetes.dynamic.resource.ResourceList):
if self.__base_resource: if self.__base_resource:
return self.__base_resource return self.__base_resource
elif self.base_resource_lookup: elif self.base_resource_lookup:
self.__base_resource = self.client.resources.get( self.__base_resource = self.client.resources.get(**self.base_resource_lookup)
**self.base_resource_lookup
)
return self.__base_resource return self.__base_resource
return None return None
def to_dict(self): def to_dict(self):
return { return {
"_type": "ResourceList", '_type': 'ResourceList',
"group": self.group, 'group': self.group,
"api_version": self.api_version, 'api_version': self.api_version,
"kind": self.kind, 'kind': self.kind,
"base_kind": self.base_kind, 'base_kind': self.base_kind,
"base_resource_lookup": self.base_resource_lookup, 'base_resource_lookup': self.base_resource_lookup
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,407 +0,0 @@
# Copyright [2021] [Red Hat, Inc.]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
from tempfile import TemporaryFile, NamedTemporaryFile
from select import select
from abc import ABCMeta, abstractmethod
import tarfile
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible.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)

View File

@@ -14,7 +14,6 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type

View File

@@ -15,8 +15,7 @@
# Implement ConfigMapHash and SecretHash equivalents # Implement ConfigMapHash and SecretHash equivalents
# Based on https://github.com/kubernetes/kubernetes/pull/49961 # Based on https://github.com/kubernetes/kubernetes/pull/49961
from __future__ import absolute_import, division, print_function from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import json import json
@@ -24,7 +23,6 @@ import hashlib
try: try:
import string import string
maketrans = string.maketrans maketrans = string.maketrans
except AttributeError: except AttributeError:
maketrans = str.maketrans maketrans = str.maketrans
@@ -46,21 +44,14 @@ def sorted_dict(unsorted_dict):
def generate_hash(resource): def generate_hash(resource):
# Get name from metadata # Get name from metadata
metada = resource.get("metadata", {}) resource['name'] = resource.get('metadata', {}).get('name', '')
key = "name" if resource['kind'] == 'ConfigMap':
resource["name"] = metada.get("name", "") marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name'])
generate_name = metada.get("generateName", "") del(resource['name'])
if resource["name"] == "" and generate_name:
del resource["name"]
key = "generateName"
resource["generateName"] = generate_name
if resource["kind"] == "ConfigMap":
marshalled = marshal(sorted_dict(resource), ["data", "kind", key])
del resource[key]
return encode(marshalled) return encode(marshalled)
if resource["kind"] == "Secret": if resource['kind'] == 'Secret':
marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"]) marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name', 'type'])
del resource[key] del(resource['name'])
return encode(marshalled) return encode(marshalled)
raise NotImplementedError raise NotImplementedError
@@ -69,10 +60,8 @@ def marshal(data, keys):
ordered = OrderedDict() ordered = OrderedDict()
for key in keys: for key in keys:
ordered[key] = data.get(key, "") ordered[key] = data.get(key, "")
return json.dumps(ordered, separators=(",", ":")).encode("utf-8") return json.dumps(ordered, separators=(',', ':')).encode('utf-8')
def encode(resource): def encode(resource):
return ( return hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
)

View File

@@ -11,14 +11,12 @@ 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()
@@ -29,11 +27,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"):
@@ -42,8 +40,7 @@ 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
@@ -63,9 +60,7 @@ 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( msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
rc, out, err
),
stdout=out, stdout=out,
stderr=err, stderr=err,
command=command, command=command,
@@ -94,11 +89,23 @@ def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
content = { content = {
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Config", "kind": "Config",
"clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}], "clusters": [
"contexts": [ {
{"context": {"cluster": "generated-cluster"}, "name": "generated-context"} "cluster": {
"server": server,
},
"name": "generated-cluster"
}
], ],
"current-context": "generated-context", "contexts": [
{
"context": {
"cluster": "generated-cluster"
},
"name": "generated-context"
}
],
"current-context": "generated-context"
} }
if not validate_certs: if not validate_certs:
@@ -107,7 +114,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
@@ -120,7 +127,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,
@@ -142,23 +149,12 @@ 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

View File

@@ -14,37 +14,26 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from kubernetes.dynamic import DynamicClient from kubernetes.dynamic import DynamicClient
from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ( from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
ApplyException,
)
class K8SDynamicClient(DynamicClient): class K8SDynamicClient(DynamicClient):
def apply(self, resource, body=None, name=None, namespace=None, **kwargs): def apply(self, resource, body=None, name=None, namespace=None):
body = super().serialize_body(body) body = super().serialize_body(body)
body["metadata"] = body.get("metadata", dict()) body['metadata'] = body.get('metadata', dict())
name = name or body["metadata"].get("name") name = name or body['metadata'].get('name')
if not name: if not name:
raise ValueError( raise ValueError("name is required to apply {0}.{1}".format(resource.group_version, resource.kind))
"name is required to apply {0}.{1}".format(
resource.group_version, resource.kind
)
)
if resource.namespaced: if resource.namespaced:
body["metadata"]["namespace"] = super().ensure_namespace( body['metadata']['namespace'] = super().ensure_namespace(resource, namespace, body)
resource, namespace, body
)
try: try:
return k8s_apply(resource, body, **kwargs) return k8s_apply(resource, body)
except ApplyException as e: except ApplyException as e:
raise ValueError( raise ValueError("Could not apply strategic merge to %s/%s: %s" %
"Could not apply strategic merge to %s/%s: %s" (body['kind'], body['metadata']['name'], e))
% (body["kind"], body["metadata"]["name"], e)
)

View File

@@ -1,79 +0,0 @@
# Copyright [2021] [Red Hat, Inc.]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class Selector(object):
equality_based_operators = ("==", "!=", "=")
def __init__(self, data):
self._operator = None
self._data = None
if not self.parse_set_based_requirement(data):
no_whitespace_data = data.replace(" ", "")
for op in self.equality_based_operators:
idx = no_whitespace_data.find(op)
if idx != -1:
self._operator = "in" if op == "==" or op == "=" else "notin"
self._key = no_whitespace_data[0:idx]
# fmt: off
self._data = [no_whitespace_data[idx + len(op):]]
# fmt: on
break
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,
)
if m:
self._set_based_requirement = True
self._key = m.group(2)
self._operator = m.group(4)
self._data = [x.replace(" ", "") for x in m.group(6).split(",") if x != ""]
return True
elif all(x not in data for x in self.equality_based_operators):
self._key = data.rstrip(" ").lstrip(" ")
if self._key.startswith("!"):
self._key = self._key[1:].lstrip(" ")
self._operator = "!"
return True
return False
def isMatch(self, labels):
if self._operator == "in":
return self._key in labels and labels.get(self._key) in self._data
elif self._operator == "notin":
return self._key not in labels or labels.get(self._key) not in self._data
else:
return (
self._key not in labels
if self._operator == "!"
else self._key in labels
)
class LabelSelectorFilter(object):
def __init__(self, label_selectors):
self.selectors = [Selector(data) for data in label_selectors]
def isMatching(self, definition):
if "metadata" not in definition or "labels" not in definition["metadata"]:
return False
labels = definition["metadata"]["labels"]
if not isinstance(labels, dict):
return None
return all(sel.isMatch(labels) for sel in self.selectors)

View File

@@ -1,18 +0,0 @@
# -*- 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

View File

@@ -4,11 +4,10 @@
# Copyright: Ansible Project # Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
--- ---
module: helm module: helm
@@ -27,10 +26,6 @@ requirements:
description: description:
- Install, upgrade, delete packages with the Helm package manager. - Install, upgrade, delete packages with the Helm package manager.
notes:
- The default idempotency check can fail to report changes when C(release_state) is set to C(present)
and C(chart_repo_url) is defined. Install helm diff >= 3.4.1 for better results.
options: options:
chart_ref: chart_ref:
description: description:
@@ -91,7 +86,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 (see Examples). - Run C(helm repo update) before the operation. Can be run as part of the package installation or as a separate step.
default: false default: false
type: bool type: bool
@@ -113,24 +108,13 @@ options:
type: bool type: bool
wait: wait:
description: description:
- When I(release_state) is set to C(present), wait until all Pods, PVCs, Services, - Wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
- When I(release_state) is set to C(absent), will wait until all the resources are deleted before returning.
It will wait for as long as I(wait_timeout). This feature requires helm>=3.7.0. Added in version 2.3.0.
default: False default: False
type: bool type: bool
wait_timeout: wait_timeout:
description: description:
- Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration). - Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).
- The use of I(wait_timeout) to wait for kubernetes commands to complete has been deprecated and will be removed after 2022-12-01.
type: str type: str
timeout:
description:
- A Go duration (described here I(https://pkg.go.dev/time#ParseDuration)) value to wait for Kubernetes commands to complete. This defaults to 5m0s.
- similar to C(wait_timeout) but does not required C(wait) to be activated.
- Mutually exclusive with C(wait_timeout).
type: str
version_added: "2.3.0"
atomic: atomic:
description: description:
- If set, the installation process deletes the installation on failure. - If set, the installation process deletes the installation on failure.
@@ -146,7 +130,6 @@ options:
description: description:
- Reuse the given name, only if that name is a deleted release which remains in the history. - Reuse the given name, only if that name is a deleted release which remains in the history.
- This is unsafe in production environment. - This is unsafe in production environment.
- mutually exclusive with with C(history_max).
type: bool type: bool
default: False default: False
version_added: "1.11.0" version_added: "1.11.0"
@@ -156,17 +139,11 @@ options:
type: bool type: bool
default: False default: False
version_added: "1.2.0" version_added: "1.2.0"
history_max:
description:
- Limit the maximum number of revisions saved per release.
- mutually exclusive with with C(replace).
type: int
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
@@ -209,13 +186,6 @@ EXAMPLES = r"""
state: absent state: absent
wait: true wait: true
- name: Separately update the repository cache
kubernetes.core.helm:
name: dummy
namespace: kube-system
state: absent
update_repo_cache: true
# From git # From git
- name: Git clone stable repo on HEAD - name: Git clone stable repo on HEAD
ansible.builtin.git: ansible.builtin.git:
@@ -260,7 +230,7 @@ EXAMPLES = r"""
enabled: True enabled: True
logging: logging:
enabled: True enabled: True
""" '''
RETURN = r""" RETURN = r"""
status: status:
@@ -319,13 +289,9 @@ 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()
@@ -336,8 +302,7 @@ 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,
) )
@@ -348,7 +313,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
@@ -367,7 +332,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
@@ -391,23 +356,9 @@ def fetch_chart_info(module, command, chart_ref):
return yaml.safe_load(out) return yaml.safe_load(out)
def deploy( def deploy(command, release_name, release_values, chart_name, wait,
command, wait_timeout, disable_hook, force, values_files, atomic=False,
release_name, create_namespace=False, replace=False, skip_crds=False):
release_values,
chart_name,
wait,
wait_timeout,
disable_hook,
force,
values_files,
history_max,
atomic=False,
create_namespace=False,
replace=False,
skip_crds=False,
timeout=None,
):
""" """
Install/upgrade/rollback release chart Install/upgrade/rollback release chart
""" """
@@ -428,9 +379,6 @@ def deploy(
if atomic: if atomic:
deploy_command += " --atomic" deploy_command += " --atomic"
if timeout:
deploy_command += " --timeout " + timeout
if force: if force:
deploy_command += " --force" deploy_command += " --force"
@@ -448,22 +396,20 @@ def deploy(
deploy_command += " --values=" + value_file deploy_command += " --values=" + value_file
if release_values != {}: if release_values != {}:
fd, path = tempfile.mkstemp(suffix=".yml") fd, path = tempfile.mkstemp(suffix='.yml')
with open(path, "w") as yaml_file: with open(path, 'w') as yaml_file:
yaml.dump(release_values, yaml_file, default_flow_style=False) yaml.dump(release_values, yaml_file, default_flow_style=False)
deploy_command += " -f=" + path deploy_command += " -f=" + path
if skip_crds: if skip_crds:
deploy_command += " --skip-crds" deploy_command += " --skip-crds"
if history_max is not None:
deploy_command += " --history-max=%s" % str(history_max)
deploy_command += " " + release_name + " " + chart_name deploy_command += " " + release_name + " " + chart_name
return deploy_command return deploy_command
def delete(command, release_name, purge, disable_hook, wait, wait_timeout): def delete(command, release_name, purge, disable_hook):
""" """
Delete release chart Delete release chart
""" """
@@ -476,12 +422,6 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
if disable_hook: if disable_hook:
delete_command += " --no-hooks" delete_command += " --no-hooks"
if wait:
delete_command += " --wait"
if wait_timeout is not None:
delete_command += " --timeout " + wait_timeout
delete_command += " " + release_name delete_command += " " + release_name
return delete_command return delete_command
@@ -490,7 +430,7 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
def load_values_files(values_files): def load_values_files(values_files):
values = {} values = {}
for values_file in values_files or []: for values_file in values_files or []:
with open(values_file, "r") as fd: with open(values_file, 'r') as fd:
content = yaml.safe_load(fd) content = yaml.safe_load(fd)
if not isinstance(content, dict): if not isinstance(content, dict):
continue continue
@@ -499,9 +439,9 @@ def load_values_files(values_files):
return values return values
def get_plugin_version(command, plugin): def has_plugin(command, plugin):
""" """
Check if helm plugin is installed and return corresponding version Check if helm plugin is installed.
""" """
cmd = command + " plugin" cmd = command + " plugin"
@@ -509,25 +449,16 @@ def get_plugin_version(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 None return False
for line in out: for line in out:
if line[0] == plugin: if line[0] == plugin:
return line[1] return True
return None return False
def helmdiff_check( def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
module, values_files=None, chart_version=None, replace=False):
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.
""" """
@@ -535,16 +466,14 @@ def helmdiff_check(
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
@@ -553,7 +482,7 @@ def helmdiff_check(
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, out.strip()) return len(out.strip()) > 0
def default_check(release_status, chart_info, values=None, values_files=None): def default_check(release_status, chart_info, values=None, values_files=None):
@@ -561,89 +490,62 @@ def default_check(release_status, chart_info, values=None, values_files=None):
Use default check to determine if release would change by upgrading a chart. Use default check to determine if release would change by upgrading a chart.
""" """
# the 'appVersion' specification is optional in a chart # the 'appVersion' specification is optional in a chart
chart_app_version = chart_info.get("appVersion", None) chart_app_version = chart_info.get('appVersion', None)
released_app_version = release_status.get("app_version", None) released_app_version = release_status.get('app_version', None)
# when deployed without an 'appVersion' chart value the 'helm list' command will return the entry `app_version: ""` # when deployed without an 'appVersion' chart value the 'helm list' command will return the entry `app_version: ""`
appversion_is_same = (chart_app_version == released_app_version) or ( appversion_is_same = (chart_app_version == released_app_version) or (chart_app_version is None and released_app_version == "")
chart_app_version is None and released_app_version == ""
)
if values_files: if values_files:
values_match = release_status["values"] == load_values_files(values_files) values_match = release_status['values'] == load_values_files(values_files)
else: else:
values_match = release_status["values"] == values values_match = release_status['values'] == values
return ( return not values_match \
not values_match or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"] \
or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
or not appversion_is_same or not appversion_is_same
)
def 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( release_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
default="present", choices=["present", "absent"], aliases=["state"] release_values=dict(type='dict', default={}, aliases=['values']),
), values_files=dict(type='list', default=[], elements='str'),
release_values=dict(type="dict", default={}, aliases=["values"]), update_repo_cache=dict(type='bool', default=False),
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( context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
type="str", kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
aliases=["kube_context"], purge=dict(type='bool', default=True),
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]), wait=dict(type='bool', default=False),
), wait_timeout=dict(type='str'),
kubeconfig=dict( atomic=dict(type='bool', default=False),
type="path", create_namespace=dict(type='bool', default=False),
aliases=["kubeconfig_path"], replace=dict(type='bool', default=False),
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]), skip_crds=dict(type='bool', default=False),
),
purge=dict(type="bool", default=True),
wait=dict(type="bool", default=False),
wait_timeout=dict(type="str"),
timeout=dict(type="str"),
atomic=dict(type="bool", default=False),
create_namespace=dict(type="bool", default=False),
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( ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
type="path", validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
aliases=["ssl_ca_cert"], api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
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"),
("wait_timeout", "timeout"),
], ],
supports_check_mode=True, supports_check_mode=True,
) )
@@ -653,33 +555,31 @@ 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")
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)
@@ -689,23 +589,11 @@ 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")
if wait: helm_cmd = delete(helm_cmd, release_name, purge, disable_hook)
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":
@@ -719,99 +607,54 @@ 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 = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
helm_cmd, disable_hook, False, values_files=values_files, atomic=atomic,
release_name, create_namespace=create_namespace, replace=replace,
release_values, skip_crds=skip_crds)
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:
helm_diff_version = get_plugin_version(helm_cmd_common, "diff") if has_plugin(helm_cmd_common, "diff") and not chart_repo_url:
if helm_diff_version and ( would_change = helmdiff_check(module, helm_cmd_common, release_name, chart_ref,
not chart_repo_url release_values, values_files, chart_version, replace)
or (
chart_repo_url
and LooseVersion(helm_diff_version) >= LooseVersion("3.4.1")
)
):
(would_change, prepared) = helmdiff_check(
module,
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( module.warn("The default idempotency check can fail to report changes in certain cases. "
"The default idempotency check can fail to report changes in certain cases. " "Install helm diff for better results.")
"Install helm diff >= 3.4.1 for better results." would_change = default_check(release_status, chart_info, release_values, values_files)
)
would_change = default_check(
release_status, chart_info, release_values, values_files
)
if force or would_change: if force or would_change:
helm_cmd = deploy( helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
helm_cmd, disable_hook, force, values_files=values_files, atomic=atomic,
release_name, create_namespace=create_namespace, replace=replace,
release_values, skip_crds=skip_crds)
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 = {"values": {"current": {}, "declared": {}}} check_status = {
'values': {
"current": {},
"declared": {},
}
}
if release_status: if release_status:
check_status["values"]["current"] = release_status["values"] check_status['values']['current'] = release_status['values']
check_status["values"]["declared"] = release_status check_status['values']['declared'] = release_status
module.exit_json( module.exit_json(
changed=changed, changed=changed,
command=helm_cmd, command=helm_cmd,
status=check_status, status=check_status,
stdout="", stdout='',
stderr="", stderr='',
**opt_result,
) )
elif not changed: elif not changed:
module.exit_json( module.exit_json(
changed=False, changed=False,
status=release_status, status=release_status,
stdout="", stdout='',
stderr="", stderr='',
command=helm_cmd, command=helm_cmd,
**opt_result,
) )
rc, out, err = run_helm(module, helm_cmd) rc, out, err = run_helm(module, helm_cmd)
@@ -822,9 +665,8 @@ 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()

View File

@@ -4,11 +4,10 @@
# Copyright: (c) 2020, Ansible Project # Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
--- ---
module: helm_info module: helm_info
@@ -39,39 +38,18 @@ options:
required: true required: true
type: str type: str
aliases: [ namespace ] aliases: [ namespace ]
release_state:
description:
- Show releases as per their states.
- Default value is C(deployed) and C(failed).
- If set to C(all), show all releases without any filter applied.
- If set to C(deployed), show deployed releases.
- If set to C(failed), show failed releases.
- If set to C(pending), show pending releases.
- If set to C(superseded), show superseded releases.
- If set to C(uninstalled), show uninstalled releases, if C(helm uninstall --keep-history) was used.
- If set to C(uninstalling), show releases that are currently being uninstalled.
type: list
elements: str
version_added: "2.3.0"
extends_documentation_fragment: extends_documentation_fragment:
- kubernetes.core.helm_common_options - kubernetes.core.helm_common_options
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Gather information of Grafana chart inside monitoring namespace - name: Deploy latest version of Grafana chart inside monitoring namespace
kubernetes.core.helm_info: kubernetes.core.helm_info:
name: test name: test
release_namespace: monitoring release_namespace: monitoring
'''
- name: Gather information about test-chart with pending state RETURN = r'''
kubernetes.core.helm_info:
name: test-chart
release_namespace: testenv
release_state:
- pending
"""
RETURN = r"""
status: status:
type: complex type: complex
description: A dictionary of status output description: A dictionary of status output
@@ -109,61 +87,40 @@ status:
type: str type: str
returned: always returned: always
description: Dict of Values used to deploy description: Dict of Values used to deploy
""" '''
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 ( from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm, get_values
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, release_state): def get_release_status(module, command, release_name):
list_command = command + " list --output=yaml" list_command = command + " list --output=yaml --filter " + release_name
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( msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
rc, out, err command=list_command
),
command=list_command,
) )
release = get_release(yaml.safe_load(out), release_name) release = get_release(yaml.safe_load(out), release_name)
@@ -171,7 +128,7 @@ def get_release_status(module, command, release_name, release_state):
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
@@ -181,43 +138,25 @@ 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( context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
type="str", kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
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( ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
type="path", validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
aliases=["ssl_ca_cert"], api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
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,
) )
@@ -225,18 +164,15 @@ 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( release_status = get_release_status(module, helm_cmd_common, release_name)
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)
@@ -244,5 +180,5 @@ def main():
module.exit_json(changed=False) module.exit_json(changed=False)
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@@ -8,7 +8,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
--- ---
module: helm_plugin module: helm_plugin
short_description: Manage Helm plugins short_description: Manage Helm plugins
@@ -24,15 +24,14 @@ options:
state: state:
description: description:
- If C(state=present) the Helm plugin will be installed. - If C(state=present) the Helm plugin will be installed.
- If C(state=latest) the Helm plugin will be updated. Added in version 2.3.0.
- If C(state=absent) the Helm plugin will be removed. - If C(state=absent) the Helm plugin will be removed.
choices: [ absent, present, latest ] choices: [ absent, present ]
default: present default: present
type: str type: str
plugin_name: plugin_name:
description: description:
- Name of Helm plugin. - Name of Helm plugin.
- Required only if C(state=absent) or C(state=latest). - Required only if C(state=absent).
type: str type: str
plugin_path: plugin_path:
description: description:
@@ -41,18 +40,11 @@ options:
machine and not on Ansible controller. machine and not on Ansible controller.
- Required only if C(state=present). - Required only if C(state=present).
type: str type: str
plugin_version:
description:
- Plugin version to install. If this is not specified, the latest version is installed.
- Ignored when C(state=absent) or C(state=latest).
required: false
type: str
version_added: "2.3.0"
extends_documentation_fragment: extends_documentation_fragment:
- kubernetes.core.helm_common_options - kubernetes.core.helm_common_options
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Install Helm env plugin - name: Install Helm env plugin
kubernetes.core.helm_plugin: kubernetes.core.helm_plugin:
plugin_path: https://github.com/adamreese/helm-env plugin_path: https://github.com/adamreese/helm-env
@@ -67,20 +59,9 @@ EXAMPLES = r"""
kubernetes.core.helm_plugin: kubernetes.core.helm_plugin:
plugin_name: env plugin_name: env
state: absent state: absent
'''
- name: Install Helm plugin with a specific version RETURN = r'''
kubernetes.core.helm_plugin:
plugin_version: 2.0.1
plugin_path: https://domain/path/to/plugin.tar.gz
state: present
- name: Update Helm plugin
kubernetes.core.helm_plugin:
plugin_name: secrets
state: latest
"""
RETURN = r"""
stdout: stdout:
type: str type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log description: Full `helm` command stdout, in case you want to display it or examine the event log
@@ -106,92 +87,67 @@ 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( state=dict(type='str', default='present', choices=['present', 'absent']),
type="str", default="present", choices=["present", "absent", "latest"] plugin_path=dict(type='str',),
), 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( context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
type="str", kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
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( ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
type="path", validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
aliases=["ssl_ca_cert"], api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
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,
@@ -199,7 +155,7 @@ def main():
command=helm_cmd_common, command=helm_cmd_common,
stdout=out, stdout=out,
stderr=err, stderr=err,
rc=rc, rc=rc
) )
elif rc == 0: elif rc == 0:
module.exit_json( module.exit_json(
@@ -219,8 +175,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())
@@ -232,7 +188,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
@@ -248,14 +204,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(
@@ -264,7 +220,7 @@ def main():
command=helm_uninstall_cmd, command=helm_uninstall_cmd,
stdout=out, stdout=out,
stderr=err, stderr=err,
rc=rc, rc=rc
) )
module.fail_json( module.fail_json(
msg="Failed to get Helm plugin uninstall", msg="Failed to get Helm plugin uninstall",
@@ -273,61 +229,7 @@ def main():
stderr=err, stderr=err,
rc=rc, rc=rc,
) )
elif state == "latest":
plugin_name = module.params.get("plugin_name")
rc, output, err = 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()

View File

@@ -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,57 +80,39 @@ 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( context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
type="str", kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
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( ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
type="path", validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
aliases=["ssl_ca_cert"], api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
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 = []
@@ -141,13 +123,21 @@ 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
@@ -161,5 +151,5 @@ def main():
) )
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@@ -4,11 +4,10 @@
# Copyright: (c) 2020, Ansible Project # Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
--- ---
module: helm_repository module: helm_repository
@@ -65,41 +64,9 @@ options:
default: present default: present
aliases: [ state ] aliases: [ state ]
type: str type: str
pass_credentials: '''
description:
- Pass credentials to all domains.
required: false
default: false
type: bool
version_added: 2.3.0
host:
description:
- Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
type: str
version_added: "2.3.0"
api_key:
description:
- Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable.
type: str
version_added: "2.3.0"
validate_certs:
description:
- Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL)
environment variable.
type: bool
aliases: [ verify_ssl ]
default: True
version_added: "2.3.0"
ca_cert:
description:
- Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable.
type: path
aliases: [ ssl_ca_cert ]
version_added: "2.3.0"
"""
EXAMPLES = r""" EXAMPLES = r'''
- name: Add a repository - name: Add a repository
kubernetes.core.helm_repository: kubernetes.core.helm_repository:
name: stable name: stable
@@ -109,9 +76,9 @@ EXAMPLES = r"""
kubernetes.core.helm_repository: kubernetes.core.helm_repository:
name: redhat-charts name: redhat-charts
repo_url: https://redhat-developer.github.com/redhat-helm-charts repo_url: https://redhat-developer.github.com/redhat-helm-charts
""" '''
RETURN = r""" RETURN = r'''
stdout: stdout:
type: str type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log description: Full `helm` command stdout, in case you want to display it or examine the event log
@@ -142,19 +109,18 @@ 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, env_fallback, missing_required_lib 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
@@ -162,7 +128,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
@@ -178,33 +144,21 @@ 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( msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
rc, out, err command=list_command
),
command=list_command,
) )
return get_repository(yaml.safe_load(out), repository_name) return get_repository(yaml.safe_load(out), repository_name)
# Install repository # Install repository
def install_repository( def install_repository(command, repository_name, repository_url, repository_username, repository_password):
command,
repository_name,
repository_url,
repository_username,
repository_password,
pass_credentials,
):
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
@@ -220,34 +174,19 @@ 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( repo_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
default="present", choices=["present", "absent"], aliases=["state"]
), ),
pass_credentials=dict(type="bool", default=False), required_together=[
# Generic auth key ['repo_username', 'repo_password']
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])), ],
ca_cert=dict( required_if=[
type="path", ('repo_state', 'present', ['repo_url']),
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,
) )
@@ -256,18 +195,17 @@ 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)
@@ -276,19 +214,10 @@ 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 = install_repository(helm_cmd, repo_name, repo_url, repo_username, repo_password)
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( module.fail_json(msg="Repository already have a repository named {0}".format(repo_name))
msg="Repository already have a repository named {0}".format(repo_name)
)
if module.check_mode: if module.check_mode:
module.exit_json(changed=changed) module.exit_json(changed=changed)
@@ -298,18 +227,16 @@ 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( msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
rc, out, err command=helm_cmd
),
command=helm_cmd,
) )
module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd) module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@@ -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,12 +57,6 @@ options:
- If the directory already exists, it will be overwritten. - If the directory already exists, it will be overwritten.
required: false required: false
type: path type: path
release_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.
@@ -70,13 +64,6 @@ options:
default: {} default: {}
aliases: [ values ] aliases: [ values ]
type: dict type: dict
show_only:
description:
- Only show manifests rendered from the given templates.
required: false
type: list
elements: str
version_added: 2.3.0
values_files: values_files:
description: description:
- Value files to pass to chart. - Value files to pass to chart.
@@ -92,9 +79,9 @@ options:
- Run C(helm repo update) before the operation. Can be run as part of the template generation or as a separate step. - 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
@@ -109,27 +96,9 @@ EXAMPLES = r"""
copy: copy:
dest: myfile.yaml dest: myfile.yaml
content: "{{ result.stdout }}" content: "{{ result.stdout }}"
'''
- name: Render MutatingWebhooksConfiguration for revision tag "canary", rev "1-13-0" RETURN = r'''
kubernetes.core.helm_template:
chart_ref: istio/istiod
chart_version: "1.13.0"
release_namespace: "istio-system"
show_only:
- "templates/revision-tags.yaml"
release_values:
revision: "1-13-0"
revisionTags:
- "canary"
register: result
- name: Write templates to file
copy:
dest: myfile.yaml
content: "{{ result.stdout }}"
"""
RETURN = r"""
stdout: stdout:
type: str type: str
description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents. description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents.
@@ -145,14 +114,13 @@ 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()
@@ -162,18 +130,8 @@ 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( def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir=None,
cmd, release_values=None, values_files=None, include_crds=False):
chart_ref,
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:
@@ -185,23 +143,16 @@ def template(
if output_dir: if output_dir:
cmd += " --output-dir=" + output_dir cmd += " --output-dir=" + output_dir
if show_only: if release_values:
for template in show_only: fd, path = tempfile.mkstemp(suffix='.yml')
cmd += " -s " + template with open(path, 'w') as yaml_file:
yaml.dump(release_values, yaml_file, default_flow_style=False)
cmd += " -f=" + path
if values_files: if values_files:
for values_file in values_files: for values_file in values_files:
cmd += " -f=" + values_file cmd += " -f=" + values_file
if release_namespace:
cmd += " -n " + release_namespace
if release_values:
fd, path = tempfile.mkstemp(suffix=".yml")
with open(path, "w") as yaml_file:
yaml.dump(release_values, yaml_file, default_flow_style=False)
cmd += " -f=" + path
if include_crds: if include_crds:
cmd += " --include-crds" cmd += " --include-crds"
@@ -211,55 +162,43 @@ def template(
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_namespace=dict(type="str"), release_values=dict(type='dict', default={}, aliases=['values']),
release_values=dict(type="dict", default={}, aliases=["values"]), values_files=dict(type='list', default=[], elements='str'),
show_only=dict(type="list", default=[], elements="str"), update_repo_cache=dict(type='bool', default=False)
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')
show_only = module.params.get("show_only") release_values = module.params.get('release_values')
release_namespace = module.params.get("release_namespace") values_files = module.params.get('values_files')
release_values = module.params.get("release_values") update_repo_cache = module.params.get('update_repo_cache')
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( tmpl_cmd = template(helm_cmd, chart_ref, chart_repo_url=chart_repo_url,
helm_cmd, chart_version=chart_version, output_dir=output_dir,
chart_ref, release_values=release_values, values_files=values_files,
chart_repo_url=chart_repo_url, include_crds=include_crds)
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)
@@ -268,9 +207,14 @@ def main():
rc = 0 rc = 0
module.exit_json( module.exit_json(
failed=False, changed=True, command=tmpl_cmd, stdout=out, stderr=err, rc=rc failed=False,
changed=True,
command=tmpl_cmd,
stdout=out,
stderr=err,
rc=rc
) )
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
module: k8s module: k8s
@@ -108,7 +108,6 @@ options:
description: description:
- Provide a valid YAML template definition file for an object when creating or updating. - Provide a valid YAML template definition file for an object when creating or updating.
- Value can be provided as string or dictionary. - Value can be provided as string or dictionary.
- The parameter accepts multiple template files. Added in version 2.0.0.
- Mutually exclusive with C(src) and C(resource_definition). - Mutually exclusive with C(src) and C(resource_definition).
- Template files needs to be present on the Ansible Controller's file system. - Template files needs to be present on the Ansible Controller's file system.
- Additional parameters can be specified using dictionary. - Additional parameters can be specified using dictionary.
@@ -136,51 +135,15 @@ options:
type: bool type: bool
default: False default: False
version_added: 2.0.0 version_added: 2.0.0
label_selectors:
description:
- Selector (label query) to filter on.
type: list
elements: str
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
@@ -245,15 +208,6 @@ EXAMPLES = r"""
variable_start_string: '[[' variable_start_string: '[['
variable_end_string: ']]' variable_end_string: ']]'
- name: Read multiple definition template file from the Ansible controller file system
kubernetes.core.k8s:
state: present
template:
- path: '/testing/deployment_one.j2'
- path: '/testing/deployment_two.j2'
variable_start_string: '[['
variable_end_string: ']]'
- name: fail on validation errors - name: fail on validation errors
kubernetes.core.k8s: kubernetes.core.k8s:
state: present state: present
@@ -308,36 +262,9 @@ EXAMPLES = r"""
metadata: metadata:
labels: labels:
support: patch support: patch
'''
# Create object using generateName RETURN = r'''
- name: create resource using name generated by the server
kubernetes.core.k8s:
state: present
generate_name: pod-
definition:
apiVersion: v1
kind: Pod
spec:
containers:
- name: py
image: python:3.7-alpine
imagePullPolicy: IfNotPresent
# Server side apply
- name: Create configmap using server side apply
kubernetes.core.k8s:
namespace: testing
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
apply: yes
server_side_apply:
field_manager: ansible
"""
RETURN = r"""
result: result:
description: description:
- The created, patched, or otherwise present object. Will be empty in the case of a deletion. - The created, patched, or otherwise present object. Will be empty in the case of a deletion.
@@ -377,34 +304,20 @@ result:
description: error while trying to create/delete the object. description: error while trying to create/delete the object.
returned: error returned: error
type: complex type: complex
""" '''
import copy import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
WAIT_ARG_SPEC,
NAME_ARG_SPEC,
RESOURCE_ARG_SPEC,
DELETE_OPTS_ARG_SPEC,
)
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),
) )
@@ -413,26 +326,15 @@ def argspec():
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC)) argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC)) argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC)) argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
argument_spec["merge_type"] = dict( argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
type="list", elements="str", choices=["json", "merge", "strategic-merge"] argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec())
) argument_spec['append_hash'] = dict(type='bool', default=False)
argument_spec["validate"] = dict(type="dict", default=None, options=validate_spec()) argument_spec['apply'] = dict(type='bool', default=False)
argument_spec["append_hash"] = dict(type="bool", default=False) argument_spec['template'] = dict(type='raw', default=None)
argument_spec["apply"] = dict(type="bool", default=False) argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
argument_spec["template"] = dict(type="raw", default=None) argument_spec['continue_on_error'] = dict(type='bool', default=False)
argument_spec["delete_options"] = dict( argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched'])
type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC) argument_spec['force'] = dict(type='bool', default=False)
)
argument_spec["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
@@ -448,11 +350,10 @@ 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.generate_name = k8s_ansible_mixin.params.get("generate_name") k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
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)
@@ -461,26 +362,19 @@ 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( module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
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, K8sAnsibleMixin, get_api_client)
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()

View File

@@ -4,11 +4,10 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
module: k8s_cluster_info module: k8s_cluster_info
version_added: "0.11.1" version_added: "0.11.1"
@@ -37,9 +36,9 @@ requirements:
- "python >= 3.6" - "python >= 3.6"
- "kubernetes >= 12.0.0" - "kubernetes >= 12.0.0"
- "PyYAML >= 3.11" - "PyYAML >= 3.11"
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Get Cluster information - name: Get Cluster information
kubernetes.core.k8s_cluster_info: kubernetes.core.k8s_cluster_info:
register: api_status register: api_status
@@ -48,9 +47,9 @@ EXAMPLES = r"""
kubernetes.core.k8s_cluster_info: kubernetes.core.k8s_cluster_info:
invalidate_cache: False invalidate_cache: False
register: api_status register: api_status
""" '''
RETURN = r""" RETURN = r'''
connection: connection:
description: description:
- Connection information - Connection information
@@ -137,7 +136,7 @@ apis:
description: Resource singular name description: Resource singular name
returned: success returned: success
type: str type: str
""" '''
import copy import copy
@@ -146,10 +145,7 @@ from collections import defaultdict
HAS_K8S = False HAS_K8S = False
try: try:
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ( from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
ResourceList,
)
HAS_K8S = True HAS_K8S = True
except ImportError as e: except ImportError as e:
K8S_IMP_ERR = e K8S_IMP_ERR = e
@@ -158,18 +154,12 @@ 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 ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule, from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
)
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( invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
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)
@@ -177,60 +167,47 @@ def execute_module(module, client):
resource = resource[0] resource = resource[0]
if isinstance(resource, ResourceList): if isinstance(resource, ResourceList):
continue continue
key = ( key = resource.group_version if resource.group == '' else '/'.join([resource.group, resource.group_version.split('/')[-1]])
resource.group_version
if resource.group == ""
else "/".join([resource.group, resource.group_version.split("/")[-1]])
)
results[key][resource.kind] = { results[key][resource.kind] = {
"categories": resource.categories if resource.categories else [], 'categories': resource.categories if resource.categories else [],
"name": resource.name, 'name': resource.name,
"namespaced": resource.namespaced, 'namespaced': resource.namespaced,
"preferred": resource.preferred, 'preferred': resource.preferred,
"short_names": resource.short_names if resource.short_names else [], 'short_names': resource.short_names if resource.short_names else [],
"singular_name": resource.singular_name, 'singular_name': resource.singular_name,
} }
configuration = client.configuration configuration = client.configuration
connection = { connection = {
"cert_file": configuration.cert_file, 'cert_file': configuration.cert_file,
"host": configuration.host, 'host': configuration.host,
"password": configuration.password, 'password': configuration.password,
"proxy": configuration.proxy, 'proxy': configuration.proxy,
"ssl_ca_cert": configuration.ssl_ca_cert, 'ssl_ca_cert': configuration.ssl_ca_cert,
"username": configuration.username, 'username': configuration.username,
"verify_ssl": configuration.verify_ssl, 'verify_ssl': configuration.verify_ssl,
} }
from kubernetes import __version__ as version from kubernetes import __version__ as version
version_info = { version_info = {
"client": version, 'client': version,
"server": client.version, 'server': client.version,
} }
module.exit_json( module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
changed=False, apis=results, connection=connection, version=version_info
)
def argspec(): def argspec():
spec = copy.deepcopy(AUTH_ARG_SPEC) spec = copy.deepcopy(AUTH_ARG_SPEC)
spec["invalidate_cache"] = dict(type="bool", default=True) spec['invalidate_cache'] = dict(type='bool', default=True)
return spec return spec
def main(): def main():
module = 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( module.fail_json(msg=missing_required_lib('kubernetes'), exception=K8S_IMP_EXC,
msg=missing_required_lib("kubernetes"), error=to_native(K8S_IMP_ERR))
exception=K8S_IMP_EXC, from ansible_collections.kubernetes.core.plugins.module_utils.common import get_api_client
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()

View File

@@ -1,221 +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: k8s_cp
short_description: Copy files and directories to and from pod.
version_added: "2.2.0"
author:
- Aubin Bikouo (@abikouo)
description:
- Use the Kubernetes Python client to copy files and directories to and from containers inside a pod.
extends_documentation_fragment:
- kubernetes.core.k8s_auth_options
requirements:
- "python >= 3.6"
- "kubernetes >= 12.0.0"
options:
namespace:
description:
- The pod namespace name.
type: str
required: yes
pod:
description:
- The pod name.
type: str
required: yes
container:
description:
- The name of the container in the pod to copy files/directories from/to.
- Defaults to the only container if there is only one container in the pod.
type: str
remote_path:
description:
- Path of the file or directory to copy.
type: path
required: yes
local_path:
description:
- Path of the local file or directory.
- Required when I(state) is set to C(from_pod).
- Mutually exclusive with I(content).
type: path
content:
description:
- When used instead of I(local_path), sets the contents of a local file directly to the specified value.
- Works only when I(remote_path) is a file. Creates the file if it does not exist.
- For advanced formatting or if the content contains a variable, use the M(ansible.builtin.template) module.
- Mutually exclusive with I(local_path).
type: str
state:
description:
- When set to C(to_pod), the local I(local_path) file or directory will be copied to I(remote_path) into the pod.
- When set to C(from_pod), the remote file or directory I(remote_path) from pod will be copied locally to I(local_path).
type: str
default: to_pod
choices: [ to_pod, from_pod ]
no_preserve:
description:
- The copied file/directory's ownership and permissions will not be preserved in the container.
- This option is ignored when I(content) is set or when I(state) is set to C(from_pod).
type: bool
default: False
notes:
- the tar binary is required on the container when copying from local filesystem to pod.
"""
EXAMPLES = r"""
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar
local_path: /tmp/foo
# kubectl cp /tmp/foo_dir some-namespace/some-pod:/tmp/bar_dir
- name: Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar_dir
local_path: /tmp/foo_dir
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar -c some-container
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
container: some-container
remote_path: /tmp/bar
local_path: /tmp/foo
no_preserve: True
state: to_pod
# kubectl cp some-namespace/some-pod:/tmp/foo /tmp/bar
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo
local_path: /tmp/bar
state: from_pod
# copy content into a file in the remote pod
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
state: to_pod
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo.txt
content: "This content will be copied into remote file"
"""
RETURN = r"""
result:
description:
- message describing the copy operation successfully done.
returned: success
type: str
"""
import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.copy import (
K8SCopyFromPod,
K8SCopyToPod,
check_pod,
)
from ansible.module_utils._text import to_native
def argspec():
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
argument_spec["namespace"] = {"type": "str", "required": True}
argument_spec["pod"] = {"type": "str", "required": True}
argument_spec["container"] = {}
argument_spec["remote_path"] = {"type": "path", "required": True}
argument_spec["local_path"] = {"type": "path"}
argument_spec["content"] = {"type": "str"}
argument_spec["state"] = {
"type": "str",
"default": "to_pod",
"choices": ["to_pod", "from_pod"],
}
argument_spec["no_preserve"] = {"type": "bool", "default": False}
return argument_spec
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.check_library_version()
k8s_ansible_mixin.module = module
k8s_ansible_mixin.argspec = module.argument_spec
k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
k8s_ansible_mixin.client = get_api_client(module=module)
containers = check_pod(k8s_ansible_mixin, module)
if len(containers) > 1 and module.params.get("container") is None:
module.fail_json(
msg="Pod contains more than 1 container, option 'container' should be set"
)
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:
k8s_copy.run()
except Exception as e:
module.fail_json("Failed to copy object due to: {0}".format(to_native(e)))
def main():
module = AnsibleModule(
argument_spec=argspec(),
mutually_exclusive=[("local_path", "content")],
required_if=[("state", "from_pod", ["local_path"])],
required_one_of=[["local_path", "content"]],
supports_check_mode=True,
)
execute_module(module)
if __name__ == "__main__":
main()

View File

@@ -1,528 +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: k8s_drain
short_description: Drain, Cordon, or Uncordon node in k8s cluster
version_added: "2.2.0"
author: Aubin Bikouo (@abikouo)
description:
- Drain node in preparation for maintenance same as kubectl drain.
- Cordon will mark the node as unschedulable.
- Uncordon will mark the node as schedulable.
- The given node will be marked unschedulable to prevent new pods from arriving.
- Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
extends_documentation_fragment:
- kubernetes.core.k8s_auth_options
options:
state:
description:
- Determines whether to drain, cordon, or uncordon node.
type: str
default: drain
choices: [ cordon, drain, uncordon ]
name:
description:
- The name of the node.
required: true
type: str
delete_options:
type: dict
description:
- Specify options to delete pods.
- This option has effect only when C(state) is set to I(drain).
suboptions:
terminate_grace_period:
description:
- Specify how many seconds to wait before forcefully terminating.
- If not specified, the default grace period for the object type will be used.
- The value zero indicates delete immediately.
required: false
type: int
force:
description:
- Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.
type: bool
default: False
ignore_daemonsets:
description:
- Ignore DaemonSet-managed pods.
type: bool
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:
description:
- Forces drain to use delete rather than evict.
type: bool
default: False
wait_timeout:
description:
- The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.
type: int
wait_sleep:
description:
- Number of seconds to sleep between checks.
- Ignored if C(wait_timeout) is not set.
default: 5
type: int
requirements:
- python >= 3.6
- kubernetes >= 12.0.0
"""
EXAMPLES = r"""
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
kubernetes.core.k8s_drain:
state: drain
name: foo
force: yes
- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
kubernetes.core.k8s_drain:
state: drain
name: foo
delete_options:
terminate_grace_period: 900
- name: Mark node "foo" as schedulable.
kubernetes.core.k8s_drain:
state: uncordon
name: foo
- name: Mark node "foo" as unschedulable.
kubernetes.core.k8s_drain:
state: cordon
name: foo
"""
RETURN = r"""
result:
description:
- The node status and the number of pods deleted.
returned: success
type: str
"""
import copy
import time
import traceback
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
try:
from kubernetes.client.api import core_v1_api
from kubernetes.client.models import V1DeleteOptions
from kubernetes.client.exceptions import ApiException
except ImportError:
# ImportError are managed by the common module already.
pass
HAS_EVICTION_API = True
k8s_import_exception = None
K8S_IMP_ERR = None
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"
daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], []
for pod in pods:
# check mirror pod: cannot be delete using API Server
if pod.metadata.annotations and k8s_kind_mirror in pod.metadata.annotations:
mirror.append((pod.metadata.namespace, pod.metadata.name))
continue
# Any finished pod can be deleted
if pod.status.phase in ("Succeeded", "Failed"):
to_delete.append((pod.metadata.namespace, pod.metadata.name))
continue
# Pod with local storage cannot be deleted
if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes):
localStorage.append((pod.metadata.namespace, pod.metadata.name))
continue
# Check replicated Pod
owner_ref = pod.metadata.owner_references
if not owner_ref:
unmanaged.append((pod.metadata.namespace, pod.metadata.name))
else:
for owner in owner_ref:
if owner.kind == "DaemonSet":
daemonSet.append((pod.metadata.namespace, pod.metadata.name))
else:
to_delete.append((pod.metadata.namespace, pod.metadata.name))
warnings, errors = [], []
if unmanaged:
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in unmanaged])
if not force:
errors.append(
"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(
pod_names
)
)
else:
# Pod not managed will be deleted as 'force' is true
warnings.append(
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
pod_names
)
)
to_delete += unmanaged
# mirror pods warning
if mirror:
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in mirror])
warnings.append(
"cannot delete mirror Pods using API server: {0}.".format(pod_names)
)
# local storage
if localStorage:
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage])
if not delete_emptydir_data:
errors.append(
"cannot delete Pods with local storage: {0}.".format(pod_names)
)
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
if daemonSet:
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in daemonSet])
if not ignore_daemonset:
errors.append(
"cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(
pod_names
)
)
else:
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
return to_delete, warnings, errors
class K8sDrainAnsible(object):
def __init__(self, module):
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
K8sAnsibleMixin,
get_api_client,
)
self._module = module
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
self._k8s_ansible_mixin.client = get_api_client(module=self._module)
self._k8s_ansible_mixin.module = self._module
self._k8s_ansible_mixin.argspec = self._module.argument_spec
self._k8s_ansible_mixin.check_mode = self._module.check_mode
self._k8s_ansible_mixin.params = self._module.params
self._k8s_ansible_mixin.fail_json = self._module.fail_json
self._k8s_ansible_mixin.fail = self._module.fail_json
self._k8s_ansible_mixin.exit_json = self._module.exit_json
self._k8s_ansible_mixin.warn = self._module.warn
self._k8s_ansible_mixin.warnings = []
self._api_instance = core_v1_api.CoreV1Api(
self._k8s_ansible_mixin.client.client
)
self._k8s_ansible_mixin.check_library_version()
# delete options
self._drain_options = module.params.get("delete_options", {})
self._delete_options = None
if self._drain_options.get("terminate_grace_period"):
self._delete_options = {}
self._delete_options.update({"apiVersion": "v1"})
self._delete_options.update({"kind": "DeleteOptions"})
self._delete_options.update(
{
"gracePeriodSeconds": self._drain_options.get(
"terminate_grace_period"
)
}
)
self._changed = False
def wait_for_pod_deletion(self, pods, wait_timeout, wait_sleep):
start = datetime.now()
def _elapsed_time():
return (datetime.now() - start).seconds
response = None
pod = pods.pop()
while (_elapsed_time() < wait_timeout or wait_timeout == 0) and pods:
if not pod:
pod = pods.pop()
try:
response = self._api_instance.read_namespaced_pod(
namespace=pod[0], name=pod[1]
)
if not response:
pod = None
time.sleep(wait_sleep)
except ApiException as exc:
if exc.reason != "Not Found":
self._module.fail_json(
msg="Exception raised: {0}".format(exc.reason)
)
pod = None
except Exception as e:
self._module.fail_json(msg="Exception raised: {0}".format(to_native(e)))
if not pods:
return None
return "timeout reached while pods were still running."
def evict_pods(self, pods):
for namespace, name in pods:
definition = {"metadata": {"name": name, "namespace": namespace}}
if self._delete_options:
definition.update({"delete_options": self._delete_options})
try:
if self._drain_options.get("disable_eviction"):
body = V1DeleteOptions(**definition)
self._api_instance.delete_namespaced_pod(
name=name, namespace=namespace, body=body
)
else:
body = v1_eviction(**definition)
self._api_instance.create_namespaced_pod_eviction(
name=name, namespace=namespace, body=body
)
self._changed = True
except ApiException as exc:
if exc.reason != "Not Found":
self._module.fail_json(
msg="Failed to delete pod {0}/{1} due to: {2}".format(
namespace, name, exc.reason
)
)
except Exception as exc:
self._module.fail_json(
msg="Failed to delete pod {0}/{1} due to: {2}".format(
namespace, name, to_native(exc)
)
)
def delete_or_evict_pods(self, node_unschedulable):
# Mark node as unschedulable
result = []
if not node_unschedulable:
self.patch_node(unschedulable=True)
result.append(
"node {0} marked unschedulable.".format(self._module.params.get("name"))
)
self._changed = True
else:
result.append(
"node {0} already marked unschedulable.".format(
self._module.params.get("name")
)
)
def _revert_node_patch():
if self._changed:
self._changed = False
self.patch_node(unschedulable=False)
try:
field_selector = "spec.nodeName={name}".format(
name=self._module.params.get("name")
)
pod_list = self._api_instance.list_pod_for_all_namespaces(
field_selector=field_selector
)
# Filter pods
force = self._drain_options.get("force", False)
ignore_daemonset = self._drain_options.get("ignore_daemonsets", False)
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:
_revert_node_patch()
self._module.fail_json(
msg="Pod deletion errors: {0}".format(" ".join(errors))
)
except ApiException as exc:
if exc.reason != "Not Found":
_revert_node_patch()
self._module.fail_json(
msg="Failed to list pod from node {name} due to: {reason}".format(
name=self._module.params.get("name"), reason=exc.reason
),
status=exc.status,
)
pods = []
except Exception as exc:
_revert_node_patch()
self._module.fail_json(
msg="Failed to list pod from node {name} due to: {error}".format(
name=self._module.params.get("name"), error=to_native(exc)
)
)
# Delete Pods
if pods:
self.evict_pods(pods)
number_pod = len(pods)
if self._drain_options.get("wait_timeout") is not None:
warn = self.wait_for_pod_deletion(
pods,
self._drain_options.get("wait_timeout"),
self._drain_options.get("wait_sleep"),
)
if warn:
warnings.append(warn)
result.append("{0} Pod(s) deleted from node.".format(number_pod))
if warnings:
return dict(result=" ".join(result), warnings=warnings)
return dict(result=" ".join(result))
def patch_node(self, unschedulable):
body = {"spec": {"unschedulable": unschedulable}}
try:
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))
)
def execute_module(self):
state = self._module.params.get("state")
name = self._module.params.get("name")
try:
node = self._api_instance.read_node(name=name)
except ApiException as exc:
if exc.reason == "Not Found":
self._module.fail_json(msg="Node {0} not found.".format(name))
self._module.fail_json(
msg="Failed to retrieve node '{0}' due to: {1}".format(
name, exc.reason
),
status=exc.status,
)
except Exception as exc:
self._module.fail_json(
msg="Failed to retrieve node '{0}' due to: {1}".format(
name, to_native(exc)
)
)
result = {}
if state == "cordon":
if node.spec.unschedulable:
self._module.exit_json(
result="node {0} already marked unschedulable.".format(name)
)
self.patch_node(unschedulable=True)
result["result"] = "node {0} marked unschedulable.".format(name)
self._changed = True
elif state == "uncordon":
if not node.spec.unschedulable:
self._module.exit_json(
result="node {0} already marked schedulable.".format(name)
)
self.patch_node(unschedulable=False)
result["result"] = "node {0} marked schedulable.".format(name)
self._changed = True
else:
# drain node
# Delete or Evict Pods
ret = self.delete_or_evict_pods(node_unschedulable=node.spec.unschedulable)
result.update(ret)
self._module.exit_json(changed=self._changed, **result)
def argspec():
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
argument_spec.update(
dict(
state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
name=dict(required=True),
delete_options=dict(
type="dict",
default={},
options=dict(
terminate_grace_period=dict(type="int"),
force=dict(type="bool", default=False),
ignore_daemonsets=dict(type="bool", default=False),
delete_emptydir_data=dict(type="bool", default=False),
disable_eviction=dict(type="bool", default=False),
wait_timeout=dict(type="int"),
wait_sleep=dict(type="int", default=5),
),
),
)
)
return argument_spec
def main():
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.execute_module()
if __name__ == "__main__":
main()

View File

@@ -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
@@ -31,7 +31,6 @@ requirements:
- "PyYAML >= 3.11" - "PyYAML >= 3.11"
notes: notes:
- Return code C(rc) for the command executed is added in output in version 2.2.0, and deprecates return code C(return_code).
- Return code C(return_code) for the command executed is added in output in version 1.0.0. - Return code C(return_code) for the command executed is added in output in version 1.0.0.
- The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource. - The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource.
@@ -40,33 +39,32 @@ options:
description: description:
- The URL of an HTTP proxy to use for the connection. - The URL of an HTTP proxy to use for the connection.
- Can also be specified via I(K8S_AUTH_PROXY) environment variable. - Can also be specified via I(K8S_AUTH_PROXY) environment variable.
- Please note that this module does not pick up typical proxy settings from the environment (for example, HTTP_PROXY). - Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
type: str type: str
namespace: namespace:
description: description:
- The pod namespace name. - The pod namespace name
type: str type: str
required: yes required: yes
pod: pod:
description: description:
- The pod name. - The pod name
type: str type: str
required: yes required: yes
container: container:
description: description:
- The name of the container in the pod to connect to. - The name of the container in the pod to connect to.
- Defaults to only container if there is only one container in the pod. - Defaults to only container if there is only one container in the pod.
- If not specified, will choose the first container from the given pod as kubectl cmdline does.
type: str type: str
required: no required: no
command: command:
description: description:
- The command to execute. - The command to execute
type: str type: str
required: yes required: yes
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Execute a command - name: Execute a command
kubernetes.core.k8s_exec: kubernetes.core.k8s_exec:
namespace: myproject namespace: myproject
@@ -84,17 +82,10 @@ EXAMPLES = r"""
- name: Check last command status - name: Check last command status
debug: debug:
msg: "cmd failed" msg: "cmd failed"
when: command_status.rc != 0 when: command_status.return_code != 0
'''
- name: Specify a container name to execute the command on RETURN = r'''
kubernetes.core.k8s_exec:
namespace: myproject
pod: busybox-test
container: manager
command: echo "hello"
"""
RETURN = r"""
result: result:
description: description:
- The command object - The command object
@@ -113,14 +104,10 @@ result:
stderr_lines: stderr_lines:
description: The command stderr description: The command stderr
type: str type: str
rc: return_code:
description: The command status code description: The command status code
type: int type: int
version_added: 2.2.0 '''
return_code:
description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
type: int
"""
import copy import copy
import shlex import shlex
@@ -131,18 +118,15 @@ except ImportError:
# ImportError are managed by the common module already. # ImportError are managed by the common module already.
pass pass
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible_collections.kubernetes.core.plugins.module_utils.common import ( from ansible_collections.kubernetes.core.plugins.module_utils.common import (
AUTH_ARG_SPEC, AUTH_ARG_SPEC
) )
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
@@ -150,10 +134,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
@@ -164,21 +148,8 @@ 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,
@@ -189,14 +160,10 @@ def execute_module(module, k8s_ansible_mixin):
stderr=True, stderr=True,
stdin=False, stdin=False,
tty=False, tty=False,
_preload_content=False, _preload_content=False, **optional_kwargs)
**optional_kwargs
)
except Exception as e: except Exception as e:
module.fail_json( module.fail_json(msg="Failed to execute on pod %s"
msg="Failed to execute on pod %s" " due to : %s" % (module.params.get('pod'), to_native(e)))
" due to : %s" % (module.params.get("pod"), to_native(e))
)
stdout, stderr, rc = [], [], 0 stdout, stderr, rc = [], [], 0
while resp.is_open(): while resp.is_open():
resp.update(timeout=1) resp.update(timeout=1)
@@ -206,37 +173,32 @@ 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.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, return_code=rc
return_code=rc,
) )
def main(): def main():
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True,) module = AnsibleModule(
from ansible_collections.kubernetes.core.plugins.module_utils.common import ( argument_spec=argspec(),
K8sAnsibleMixin, supports_check_mode=True,
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()

View File

@@ -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,17 +144,12 @@ resources:
description: Current status details for the object. description: Current status details for the object.
returned: success returned: success
type: dict type: dict
""" '''
import copy import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule, from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC, WAIT_ARG_SPEC)
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
WAIT_ARG_SPEC,
)
def execute_module(module, k8s_ansible_mixin): def execute_module(module, k8s_ansible_mixin):
@@ -179,11 +174,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
@@ -192,18 +187,12 @@ 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, K8sAnsibleMixin, get_api_client)
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()

View File

@@ -8,14 +8,14 @@ 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
description: description:
- This module is used to apply RFC 6902 JSON patch operations only. - This module is used to apply RFC 6902 JSON patch operations only.
- Use the M(kubernetes.core.k8s) module for strategic merge or JSON merge operations. - Use the M(k8s) module for strategic merge or JSON merge operations.
- The jsonpatch library is required for check mode. - The jsonpatch library is required for check mode.
version_added: 2.0.0 version_added: 2.0.0
@@ -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,24 +122,17 @@ error:
"msg": "Failed to import the required Python library (jsonpatch) ...", "msg": "Failed to import the required Python library (jsonpatch) ...",
"exception": "Traceback (most recent call last): ..." "exception": "Traceback (most recent call last): ..."
} }
""" '''
import copy import copy
import traceback import traceback
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule, from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC
)
from ansible_collections.kubernetes.core.plugins.module_utils.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, get_api_client, K8sAnsibleMixin)
K8sAnsibleMixin,
)
try: try:
from kubernetes.dynamic.exceptions import DynamicApiError from kubernetes.dynamic.exceptions import DynamicApiError
@@ -150,7 +143,6 @@ except ImportError:
JSON_PATCH_IMPORT_ERR = None JSON_PATCH_IMPORT_ERR = None
try: try:
import jsonpatch import jsonpatch
HAS_JSON_PATCH = True HAS_JSON_PATCH = True
except ImportError: except ImportError:
HAS_JSON_PATCH = False HAS_JSON_PATCH = False
@@ -158,18 +150,33 @@ except ImportError:
JSON_PATCH_ARGS = { JSON_PATCH_ARGS = {
"api_version": {"default": "v1", "aliases": ["api", "version"]}, 'api_version': {
"kind": {"type": "str", "required": True}, 'default': 'v1',
"namespace": {"type": "str"}, 'aliases': ['api', 'version'],
"name": {"type": "str", "required": True}, },
"patch": {"type": "list", "required": True, "elements": "dict"}, "kind": {
"type": "str",
"required": True,
},
"namespace": {
"type": "str",
},
"name": {
"type": "str",
"required": True,
},
"patch": {
"type": "list",
"required": True,
"elements": "dict",
},
} }
def json_patch(existing, patch): def json_patch(existing, patch):
if not HAS_JSON_PATCH: if not HAS_JSON_PATCH:
error = { error = {
"msg": missing_required_lib("jsonpatch"), "msg": missing_required_lib('jsonpatch'),
"exception": JSON_PATCH_IMPORT_ERR, "exception": JSON_PATCH_IMPORT_ERR,
} }
return None, error return None, error
@@ -178,10 +185,16 @@ def json_patch(existing, patch):
patched = patch.apply(existing) patched = patch.apply(existing)
return patched, None return patched, None
except jsonpatch.InvalidJsonPatch as e: except jsonpatch.InvalidJsonPatch as e:
error = {"msg": "Invalid JSON patch", "exception": e} error = {
"msg": "Invalid JSON patch",
"exception": e
}
return None, error return None, error
except jsonpatch.JsonPatchConflict as e: except jsonpatch.JsonPatchConflict as e:
error = {"msg": "Patch could not be applied due to a conflict", "exception": e} error = {
"msg": "Patch could not be applied due to a conflict",
"exception": e
}
return None, error return None, error
@@ -196,14 +209,15 @@ 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( if module.params.get("wait_condition") and module.params.get("wait_condition").get("type"):
"type" wait_condition = module.params['wait_condition']
):
wait_condition = module.params["wait_condition"]
# definition is needed for wait # definition is needed for wait
definition = { definition = {
"kind": kind, "kind": kind,
"metadata": {"name": name, "namespace": namespace}, "metadata": {
"name": name,
"namespace": namespace,
}
} }
def build_error_msg(kind, name, msg): def build_error_msg(kind, name, msg):
@@ -214,53 +228,32 @@ 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( module.fail_json(msg=build_error_msg(kind, name, msg), error=exc.status, status=exc.status, reason=exc.reason)
msg=build_error_msg(kind, name, msg),
error=exc.status,
status=exc.status,
reason=exc.reason,
)
except ValueError as exc: except ValueError as exc:
msg = "Failed to retrieve requested object: {0}".format(to_native(exc)) msg = 'Failed to retrieve requested object: {0}'.format(to_native(exc))
module.fail_json( module.fail_json(msg=build_error_msg(kind, name, msg), error='', status='', reason='')
msg=build_error_msg(kind, name, msg), error="", status="", reason=""
)
if module.check_mode and not k8s_module.supports_dry_run: if module.check_mode:
obj, error = json_patch(existing.to_dict(), patch) obj, error = json_patch(existing.to_dict(), patch)
if error: if error:
module.fail_json(**error) module.fail_json(**error)
else: else:
params = {}
if module.check_mode:
params["dry_run"] = "All"
try: try:
obj = resource.patch( obj = resource.patch(patch, name=name, namespace=namespace, content_type="application/json-patch+json").to_dict()
patch,
name=name,
namespace=namespace,
content_type="application/json-patch+json",
**params
).to_dict()
except DynamicApiError as exc: except DynamicApiError as exc:
msg = "Failed to patch existing object: {0}".format(exc.body) msg = "Failed to patch existing object: {0}".format(exc.body)
module.fail_json( module.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
msg=msg, error=exc.status, status=exc.status, reason=exc.reason
)
except Exception as exc: except Exception as exc:
msg = "Failed to patch existing object: {0}".format(exc) msg = "Failed to patch existing object: {0}".format(exc)
module.fail_json(msg=msg, error=to_native(exc), status="", reason="") module.fail_json(msg=msg, error=to_native(exc), status='', reason='')
success = True success = True
result = {"result": obj} result = {"result": obj}
if wait and not module.check_mode: if wait and not module.check_mode:
success, result["result"], result["duration"] = k8s_module.wait( success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
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:
result["diff"] = diffs result["diff"] = diffs
if not success: if not success:

View File

@@ -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
@@ -54,20 +54,14 @@ options:
- If there is more than one container, this option is required. - If there is more than one container, this option is required.
required: no required: no
type: str type: str
since_seconds:
description:
- A relative time in seconds before the current time from which to show logs.
required: no
type: str
version_added: '2.2.0'
requirements: requirements:
- "python >= 3.6" - "python >= 3.6"
- "kubernetes >= 12.0.0" - "kubernetes >= 12.0.0"
- "PyYAML >= 3.11" - "PyYAML >= 3.11"
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Get a log from a Pod - name: Get a log from a Pod
kubernetes.core.k8s_log: kubernetes.core.k8s_log:
name: example-1 name: example-1
@@ -89,7 +83,6 @@ EXAMPLES = r"""
kind: Deployment kind: Deployment
namespace: testing namespace: testing
name: example name: example
since_seconds: "4000"
register: log register: log
# This will get the log from a single Pod managed by this DeploymentConfig # This will get the log from a single Pod managed by this DeploymentConfig
@@ -100,9 +93,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,20 +106,15 @@ log_lines:
description: description:
- The log of the object, split on newlines - The log of the object, split on newlines
returned: success returned: success
""" '''
import copy import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible.module_utils.six import PY2 from ansible.module_utils.six import PY2
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 argspec(): def argspec():
@@ -134,62 +122,51 @@ 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(), 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( resource = k8s_ansible_mixin.find_resource(module.params['kind'], module.params['api_version'], fail=True)
module.params["kind"], module.params["api_version"], fail=True v1_pods = k8s_ansible_mixin.find_resource('Pod', 'v1', fail=True)
)
v1_pods = k8s_ansible_mixin.find_resource("Pod", "v1", fail=True)
if "log" not in resource.subresources: if 'log' not in resource.subresources:
if not name: if not name:
module.fail( module.fail(msg='name must be provided for resources that do not support the log subresource')
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( module.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
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"): log = serialize_log(resource.log.get(
kwargs.setdefault("query_params", {}).update( name=name,
{"sinceSeconds": module.params["since_seconds"]} namespace=namespace,
) serialize=False,
**kwargs
))
log = serialize_log( module.exit_json(changed=False, log=log, log_lines=log.split('\n'))
resource.log.get(name=name, namespace=namespace, serialize=False, **kwargs)
)
module.exit_json(changed=False, log=log, log_lines=log.split("\n"))
def extract_selectors(module, instance): def extract_selectors(module, instance):
@@ -197,46 +174,35 @@ 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( module.fail(msg='{0} {1} does not support the log subresource directly, and no Pod selector was found on the object'.format(
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))
"/".join(instance.group, instance.apiVersion), instance.kind
)
)
if not ( if not (instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions):
instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions
):
# A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions # A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
for k, v in dict(instance.spec.selector).items(): for k, v in dict(instance.spec.selector).items():
selectors.append("{0}={1}".format(k, v)) selectors.append('{0}={1}'.format(k, v))
return selectors return selectors
if instance.spec.selector.matchLabels: if instance.spec.selector.matchLabels:
for k, v in dict(instance.spec.selector.matchLabels).items(): for k, v in dict(instance.spec.selector.matchLabels).items():
selectors.append("{0}={1}".format(k, v)) selectors.append('{0}={1}'.format(k, v))
if instance.spec.selector.matchExpressions: if instance.spec.selector.matchExpressions:
for expression in instance.spec.selector.matchExpressions: for expression in instance.spec.selector.matchExpressions:
operator = expression.operator operator = expression.operator
if operator == "Exists": if operator == 'Exists':
selectors.append(expression.key) selectors.append(expression.key)
elif operator == "DoesNotExist": elif operator == 'DoesNotExist':
selectors.append("!{0}".format(expression.key)) selectors.append('!{0}'.format(expression.key))
elif operator in ["In", "NotIn"]: elif operator in ['In', 'NotIn']:
selectors.append( selectors.append('{key} {operator} {values}'.format(
"{key} {operator} {values}".format(
key=expression.key, key=expression.key,
operator=operator.lower(), operator=operator.lower(),
values="({0})".format(", ".join(expression.values)), values='({0})'.format(', '.join(expression.values))
) ))
)
else: else:
module.fail( module.fail(msg='The k8s_log module does not support the {0} matchExpression operator'.format(operator.lower()))
msg="The k8s_log module does not support the {0} matchExpression operator".format(
operator.lower()
)
)
return selectors return selectors
@@ -244,20 +210,18 @@ 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, K8sAnsibleMixin, get_api_client)
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()

View File

@@ -5,11 +5,10 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
module: k8s_rollback module: k8s_rollback
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
version_added: "1.0.0" version_added: "1.0.0"
@@ -35,18 +34,18 @@ requirements:
- "python >= 3.6" - "python >= 3.6"
- "kubernetes >= 12.0.0" - "kubernetes >= 12.0.0"
- "PyYAML >= 3.11" - "PyYAML >= 3.11"
""" '''
EXAMPLES = r""" EXAMPLES = r'''
- name: Rollback a failed deployment - name: Rollback a failed deployment
kubernetes.core.k8s_rollback: kubernetes.core.k8s_rollback:
api_version: apps/v1 api_version: apps/v1
kind: Deployment kind: Deployment
name: web name: web
namespace: testing namespace: testing
""" '''
RETURN = r""" RETURN = r'''
rollback_info: rollback_info:
description: description:
- The object that was rolled back. - The object that was rolled back.
@@ -75,29 +74,25 @@ rollback_info:
description: Current status details for the object. description: Current status details for the object.
returned: success returned: success
type: dict type: dict
""" '''
import copy import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, AUTH_ARG_SPEC, NAME_ARG_SPEC)
NAME_ARG_SPEC,
)
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
@@ -107,100 +102,80 @@ 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'])
)
changed = False for resource in resources['resources']:
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(**{"changed": changed, "rollback_info": results}) module.exit_json(**{
'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"][ current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
"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"]["selector"]["matchLabels"], resource['spec']
"", ['selector']
) ['matchLabels'],
'')
prev_managed_resource = get_previous_revision( prev_managed_resource = get_previous_revision(managed_resources['resources'],
managed_resources["resources"], current_revision 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"][ del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
"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[ "deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
"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}".format( "/apis/{0}/namespaces/{1}/{2}/{3}"
module.params["api_version"], .format(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 result['resources'] = rollback.to_dict()
return result return result
@@ -209,8 +184,8 @@ def argspec():
args.update(NAME_ARG_SPEC) args.update(NAME_ARG_SPEC)
args.update( args.update(
dict( dict(
label_selectors=dict(type="list", elements="str", default=[]), label_selectors=dict(type='list', elements='str', default=[]),
field_selectors=dict(type="list", elements="str", default=[]), field_selectors=dict(type='list', elements='str', default=[]),
) )
) )
return args return args
@@ -218,40 +193,27 @@ def argspec():
def get_previous_revision(all_resources, current_revision): def get_previous_revision(all_resources, current_revision):
for resource in all_resources: for resource in all_resources:
if resource["kind"] == "ReplicaSet": if resource['kind'] == 'ReplicaSet':
if ( if int(resource['metadata']
int( ['annotations']
resource["metadata"]["annotations"][ ['deployment.kubernetes.io/revision']) == int(current_revision) - 1:
"deployment.kubernetes.io/revision"
]
)
== int(current_revision) - 1
):
return resource return resource
elif resource["kind"] == "ControllerRevision": elif resource['kind'] == 'ControllerRevision':
if ( if int(resource['metadata']
int( ['annotations']
resource["metadata"]["annotations"][ ['deprecated.daemonset.template.generation']) == int(current_revision) - 1:
"deprecated.daemonset.template.generation"
]
)
== int(current_revision) - 1
):
return resource return resource
return None return None
def main(): def main():
module = 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()

View File

@@ -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,169 +139,129 @@ 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 ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_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( def execute_module(module, k8s_ansible_mixin,):
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(), diff=dict())
if module._diff:
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 ( from ansible_collections.kubernetes.core.plugins.module_utils.common import NotFoundError
NotFoundError,
)
multiple_scale = False multiple_scale = False
try: try:
existing = resource.get( existing = resource.get(name=name, namespace=namespace, label_selector=','.join(label_selectors))
name=name, namespace=namespace, label_selector=",".join(label_selectors) if existing.kind.endswith('List'):
)
if existing.kind.endswith("List"):
existing_items = existing.items existing_items = existing.items
multiple_scale = len(existing_items) > 1 multiple_scale = len(existing_items) > 1
else: else:
existing_items = [existing] existing_items = [existing]
except NotFoundError as exc: except NotFoundError as exc:
module.fail_json( module.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
msg="Failed to retrieve requested object: {0}".format(exc), error=exc.value.get('status'))
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"].lower() == "job": if module.params['kind'] == 'job':
existing_count = existing.spec.parallelism existing_count = existing.spec.parallelism
elif hasattr(existing.spec, "replicas"): elif hasattr(existing.spec, 'replicas'):
existing_count = existing.spec.replicas existing_count = existing.spec.replicas
if existing_count is None: if existing_count is None:
error = "Failed to retrieve the available count for object kind={0} name={1} namespace={2}.".format( error = 'Failed to retrieve the available count for object kind={0} name={1} namespace={2}.'.format(
existing.kind, existing.metadata.name, existing.metadata.namespace existing.kind, existing.metadata.name, existing.metadata.namespace)
)
_continue_or_fail(error) _continue_or_fail(error)
continue continue
if resource_version and resource_version != existing.metadata.resourceVersion: if resource_version and resource_version != existing.metadata.resourceVersion:
warn = "expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format( warn = 'expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
resource_version, resource_version, existing.metadata.resourceVersion, existing.kind, existing.metadata.name, existing.metadata.namespace)
existing.metadata.resourceVersion,
existing.kind,
existing.metadata.name,
existing.metadata.namespace,
)
_continue_or_exit(warn) _continue_or_exit(warn)
continue continue
if current_replicas is not None and existing_count != current_replicas: if current_replicas is not None and existing_count != current_replicas:
warn = "current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format( warn = 'current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.'.format(
current_replicas, current_replicas, existing_count, existing.kind, existing.metadata.name, existing.metadata.namespace)
existing_count,
existing.kind,
existing.metadata.name,
existing.metadata.namespace,
)
_continue_or_exit(warn) _continue_or_exit(warn)
continue continue
if existing_count != replicas: if existing_count != replicas:
if module.params["kind"].lower() == "job": if not module.check_mode:
if module.params['kind'] == 'job':
existing.spec.parallelism = replicas existing.spec.parallelism = replicas
result = {"changed": True} result = resource.patch(existing.to_dict()).to_dict()
if module.check_mode:
result["result"] = existing.to_dict()
else: else:
result["result"] = resource.patch(existing.to_dict()).to_dict() result = scale(module, k8s_ansible_mixin, resource, existing, replicas, wait, wait_time, wait_sleep)
else: changed = changed or result['changed']
result = scale(
module,
k8s_ansible_mixin,
resource,
existing,
replicas,
wait,
wait_time,
wait_sleep,
)
changed = changed or result["changed"]
else: 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(), 'diff': {}}
if module._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)
@@ -313,87 +273,55 @@ def argspec():
args.update(RESOURCE_ARG_SPEC) args.update(RESOURCE_ARG_SPEC)
args.update(NAME_ARG_SPEC) args.update(NAME_ARG_SPEC)
args.update(AUTH_ARG_SPEC) args.update(AUTH_ARG_SPEC)
args.update({"label_selectors": {"type": "list", "elements": "str", "default": []}}) args.update({'label_selectors': {'type': 'list', 'elements': 'str', 'default': []}})
args.update(({"continue_on_error": {"type": "bool", "default": False}})) args.update(({'continue_on_error': {'type': 'bool', 'default': False}}))
return args return args
def scale( def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait, wait_time, wait_sleep):
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 = { scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
"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()
result["result"] = k8s_obj match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
if wait and not module.check_mode: result = dict()
success, result["result"], result["duration"] = k8s_ansible_mixin.wait( result['result'] = k8s_obj
resource, scale_obj, wait_sleep, wait_time result['changed'] = not match
) 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( module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
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, K8sAnsibleMixin, get_api_client)
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()

View File

@@ -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,36 +140,32 @@ result:
description: Current status details for the object. description: Current status details for the object.
returned: success returned: success
type: complex type: complex
""" '''
import copy import copy
from collections import defaultdict from collections import defaultdict
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC, AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC)
COMMON_ARG_SPEC,
RESOURCE_ARG_SPEC,
)
SERVICE_ARG_SPEC = { SERVICE_ARG_SPEC = {
"apply": {"type": "bool", "default": False}, 'apply': {
"name": {"required": True}, 'type': 'bool',
"namespace": {"required": True}, 'default': False,
"merge_type": {
"type": "list",
"elements": "str",
"choices": ["json", "merge", "strategic-merge"],
}, },
"selector": {"type": "dict"}, 'name': {'required': True},
"type": { 'namespace': {'required': True},
"type": "str", 'merge_type': {'type': 'list', 'elements': 'str', 'choices': ['json', 'merge', 'strategic-merge']},
"choices": ["NodePort", "ClusterIP", "LoadBalancer", "ExternalName"], 'selector': {'type': 'dict'},
'type': {
'type': 'str',
'choices': [
'NodePort', 'ClusterIP', 'LoadBalancer', 'ExternalName'
],
}, },
"ports": {"type": "list", "elements": "dict"}, 'ports': {'type': 'list', 'elements': 'dict'},
} }
@@ -199,31 +195,29 @@ 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( definition = dict(merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition))
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)
@@ -233,14 +227,12 @@ 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, K8sAnsibleMixin, get_api_client)
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()

View File

@@ -1,310 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Alina Buzachis <@alinabuzachis>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: k8s_taint
short_description: Taint a node in a Kubernetes/OpenShift cluster
version_added: "2.3.0"
author: Alina Buzachis (@alinabuzachis)
description:
- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
- Untaint will remove taints from nodes as needed.
extends_documentation_fragment:
- kubernetes.core.k8s_auth_options
options:
state:
description:
- Determines whether to add or remove taints.
type: str
default: present
choices: [ present, absent ]
name:
description:
- The name of the node.
required: true
type: str
taints:
description:
- List containing the taints.
type: list
required: true
elements: dict
suboptions:
key:
description:
- The taint key to be applied to a node.
type: str
value:
description:
- The taint value corresponding to the taint key.
type: str
effect:
description:
- The effect of the taint on Pods that do not tolerate the taint.
- Required when I(state=present).
type: str
choices: [ NoSchedule, NoExecute, PreferNoSchedule ]
replace:
description:
- If C(true), allow taints to be replaced.
required: false
default: false
type: bool
requirements:
- python >= 3.6
- kubernetes >= 12.0.0
"""
EXAMPLES = r"""
- name: Taint node "foo"
kubernetes.core.k8s_taint:
state: present
name: foo
taints:
- effect: NoExecute
key: "key1"
- name: Taint node "foo"
kubernetes.core.k8s_taint:
state: present
name: foo
taints:
- effect: NoExecute
key: "key1"
value: "value1"
- effect: NoSchedule
key: "key1"
value: "value1"
- name: Remove taint from "foo".
kubernetes.core.k8s_taint:
state: absent
name: foo
taints:
- effect: NoExecute
key: "key1"
value: "value1"
"""
RETURN = r"""
result:
description:
- The tainted Node object. Will be empty in the case of a deletion.
returned: success
type: complex
contains:
api_version:
description: The versioned schema of this representation of an object.
returned: success
type: str
kind:
description: Represents the REST resource this object represents.
returned: success
type: str
metadata:
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
returned: success
type: complex
spec:
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
returned: success
type: complex
status:
description: Current status details for the object.
returned: success
type: complex
"""
import copy
from ansible.module_utils.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()

View File

@@ -1,8 +1,7 @@
# slow - 11min
slow slow
time=313 time=609
helm_info helm_info
helm_plugin
helm_plugin_info
helm_repository helm_repository
helm_template helm_template
helm_plugin
helm_plugin_info

View File

@@ -1,6 +1,7 @@
--- ---
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
chart_test: "ingress-nginx" chart_test: "ingress-nginx"
chart_test_local_path: "nginx-ingress" chart_test_local_path: "nginx-ingress"
@@ -13,15 +14,3 @@ 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"

View File

@@ -1,9 +1,9 @@
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: foos.ansible.com name: foos.example.com
spec: spec:
group: ansible.com group: example.com
versions: versions:
- name: v1 - name: v1
served: true served: true

View File

@@ -1,95 +0,0 @@
#!/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()

View File

@@ -1,5 +0,0 @@
---
collections:
- kubernetes.core
dependencies:
- remove_namespace

View File

@@ -33,9 +33,6 @@
- name: Test helm diff - name: Test helm diff
include_tasks: tests_helm_diff.yml include_tasks: tests_helm_diff.yml
- name: Test helm uninstall
include_tasks: test_helm_uninstall.yml
# https://github.com/ansible-collections/community.kubernetes/issues/296 # https://github.com/ansible-collections/community.kubernetes/issues/296
- name: Test Skip CRDS feature in helm chart install - name: Test Skip CRDS feature in helm chart install
include_tasks: test_crds.yml include_tasks: test_crds.yml

View File

@@ -6,7 +6,7 @@
- name: Create namespace - name: Create namespace
k8s: k8s:
kind: Namespace kind: Namespace
name: "{{ test_namespace[4] }}" name: "{{ helm_namespace }}"
- name: Copy test chart - name: Copy test chart
copy: copy:
@@ -17,7 +17,7 @@
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}" chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
namespace: "{{ test_namespace[4] }}" namespace: "{{ helm_namespace }}"
name: test-crds name: test-crds
skip_crds: true skip_crds: true
register: install register: install
@@ -30,10 +30,10 @@
- name: Fail to create custom resource - name: Fail to create custom resource
k8s: k8s:
definition: definition:
apiVersion: ansible.com/v1 apiVersion: example.com/v1
kind: Foo kind: Foo
metadata: metadata:
namespace: "{{ test_namespace[4] }}" namespace: "{{ helm_namespace }}"
name: test-foo name: test-foo
foobar: footest foobar: footest
ignore_errors: true ignore_errors: true
@@ -42,13 +42,13 @@
- assert: - assert:
that: that:
- result is failed - result is failed
- "result.msg.startswith('Failed to find exact match for ansible.com/v1.Foo')" - "result.msg.startswith('Failed to find exact match for example.com/v1.Foo')"
# Helm won't install CRDs into an existing release, so we need to delete this, first # Helm won't install CRDs into an existing release, so we need to delete this, first
- name: Uninstall chart - name: Uninstall chart
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
namespace: "{{ test_namespace[4] }}" namespace: "{{ helm_namespace }}"
name: test-crds name: test-crds
state: absent state: absent
@@ -56,16 +56,16 @@
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}" chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
namespace: "{{ test_namespace[4] }}" namespace: "{{ helm_namespace }}"
name: test-crds name: test-crds
- name: Create custom resource - name: Create custom resource
k8s: k8s:
definition: definition:
apiVersion: ansible.com/v1 apiVersion: example.com/v1
kind: Foo kind: Foo
metadata: metadata:
namespace: "{{ test_namespace[4] }}" namespace: "{{ helm_namespace }}"
name: test-foo name: test-foo
foobar: footest foobar: footest
register: result register: result
@@ -85,14 +85,16 @@
- name: Remove namespace - name: Remove namespace
k8s: k8s:
kind: Namespace kind: Namespace
name: "{{ test_namespace[4] }}" name: "{{ helm_namespace }}"
state: absent state: absent
wait: true
wait_timeout: 180
ignore_errors: true ignore_errors: true
# CRDs aren't deleted with a namespace, so we need to manually delete it # CRDs aren't deleted with a namespace, so we need to manually delete it
- name: Remove CRD - name: Remove CRD
k8s: k8s:
kind: CustomResourceDefinition kind: CustomResourceDefinition
name: foos.ansible.com name: foos.example.com
state: absent state: absent
ignore_errors: true ignore_errors: true

View File

@@ -4,7 +4,7 @@
binary_path: "{{ helm_binary}}_fake" binary_path: "{{ helm_binary}}_fake"
name: test name: test
chart_ref: "{{ chart_test }}" chart_ref: "{{ chart_test }}"
namespace: "{{ test_namespace[3] }}" namespace: "{{ helm_namespace }}"
ignore_errors: yes ignore_errors: yes
register: helm_missing_binary register: helm_missing_binary

View File

@@ -1,80 +0,0 @@
- name: validate helm version lower than 3.7.0
helm_test_version:
binary_path: "{{ helm_binary }}"
version: "3.7.0"
register: test_version
- block:
- set_fact:
chart_source: "https://github.com/kubernetes/kube-state-metrics/releases/download/kube-state-metrics-helm-chart-2.13.3/kube-state-metrics-2.13.3.tgz"
chart_name: "test-wait-uninstall"
helm_namespace: "{{ test_namespace[2] }}"
- name: Install chart
helm:
binary_path: "{{ helm_binary }}"
name: "{{ chart_name }}"
chart_ref: "{{ chart_source }}"
namespace: "{{ helm_namespace }}"
create_namespace: true
- name: Delete chart with wait
helm:
state: absent
binary_path: "{{ helm_binary }}"
name: "{{ chart_name }}"
namespace: "{{ helm_namespace }}"
wait: yes
register: uninstall
- name: assert warning has been raised
assert:
that:
- uninstall.warnings
- name: Create temp directory
tempfile:
state: directory
suffix: .test
register: _result
- set_fact:
helm_tmp_dir: "{{ _result.path }}"
- name: Unarchive Helm binary
unarchive:
src: 'https://get.helm.sh/helm-v3.7.0-linux-amd64.tar.gz'
dest: "{{ helm_tmp_dir }}"
remote_src: yes
- name: Install chart
helm:
binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
name: "{{ chart_name }}"
chart_ref: "{{ chart_source }}"
namespace: "{{ helm_namespace }}"
create_namespace: true
- name: uninstall chart again using recent version
helm:
state: absent
binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
name: "{{ chart_name }}"
namespace: "{{ helm_namespace }}"
wait: yes
register: uninstall
always:
- name: Delete temp directory
file:
path: "{{ helm_tmp_dir }}"
state: absent
ignore_errors: true
- name: Remove namespace
k8s:
kind: Namespace
name: "{{ helm_namespace }}"
state: absent
ignore_errors: true
when: test_version.result

View File

@@ -3,7 +3,7 @@
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
state: absent state: absent
name: does-not-exist name: does-not-exist
namespace: "{{ test_namespace[1] }}" namespace: "{{ helm_namespace }}"
environment: environment:
K8S_AUTH_HOST: somewhere K8S_AUTH_HOST: somewhere
register: _helm_result register: _helm_result

View File

@@ -1,8 +1,5 @@
--- ---
- name: Chart tests - name: Chart tests
vars:
chart_release_name: "test-{{ chart_name | default(source) }}"
chart_release_replaced_name: "test-{{ chart_name | default(source) }}-001"
block: block:
- name: Create temp directory - name: Create temp directory
tempfile: tempfile:
@@ -16,7 +13,7 @@
- name: Check helm_info empty - name: Check helm_info empty
helm_info: helm_info:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: empty_info register: empty_info
@@ -28,7 +25,7 @@
- name: "Install fail {{ chart_test }} from {{ source }}" - name: "Install fail {{ chart_test }} from {{ source }}"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -44,7 +41,7 @@
- name: "Install {{ chart_test }} from {{ source }} in check mode" - name: "Install {{ chart_test }} from {{ source }} in check mode"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -62,7 +59,7 @@
- name: "Install {{ chart_test }} from {{ source }}" - name: "Install {{ chart_test }} from {{ source }}"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -79,30 +76,20 @@
- name: Check helm_info content - name: Check helm_info content
helm_info: helm_info:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: content_info register: content_info
- name: Check helm_info content using release_state
helm_info:
binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}"
namespace: "{{ helm_namespace }}"
release_state:
- deployed
register: release_state_content_info
- name: "Assert that {{ chart_test }} is installed from {{ source }} with helm_info" - name: "Assert that {{ chart_test }} is installed from {{ source }} with helm_info"
assert: assert:
that: that:
- content_info.status.chart == "{{ chart_test }}-{{ chart_test_version }}" - content_info.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
- content_info.status.status | lower == 'deployed' - content_info.status.status | lower == 'deployed'
- release_state_content_info.status.status | lower == 'deployed'
- name: Check idempotency - name: Check idempotency
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -118,7 +105,7 @@
- name: "Add vars to {{ chart_test }} from {{ source }}" - name: "Add vars to {{ chart_test }} from {{ source }}"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -136,7 +123,7 @@
- name: Check idempotency after adding vars - name: Check idempotency after adding vars
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -154,7 +141,7 @@
- name: "Remove Vars to {{ chart_test }} from {{ source }}" - name: "Remove Vars to {{ chart_test }} from {{ source }}"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -171,7 +158,7 @@
- name: Check idempotency after removing vars - name: Check idempotency after removing vars
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -188,7 +175,7 @@
- name: "Upgrade {{ chart_test }} from {{ source }}" - name: "Upgrade {{ chart_test }} from {{ source }}"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source_upgrade | default(chart_source) }}" chart_ref: "{{ chart_source_upgrade | default(chart_source) }}"
chart_version: "{{ chart_source_version_upgrade | default(omit) }}" chart_version: "{{ chart_source_version_upgrade | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -204,7 +191,7 @@
- name: Check idempotency after upgrade - name: Check idempotency after upgrade
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source_upgrade | default(chart_source) }}" chart_ref: "{{ chart_source_upgrade | default(chart_source) }}"
chart_version: "{{ chart_source_version_upgrade | default(omit) }}" chart_version: "{{ chart_source_version_upgrade | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -221,7 +208,7 @@
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
state: absent state: absent
name: "{{ chart_release_name }}" name: test
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: install register: install
@@ -234,7 +221,7 @@
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
state: absent state: absent
name: "{{ chart_release_name }}" name: test
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: install register: install
@@ -247,7 +234,7 @@
- name: Install chart for replace option - name: Install chart for replace option
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_replaced_name }}" name: test-0001
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -258,11 +245,11 @@
that: that:
- install is changed - install is changed
- name: "Remove {{ chart_release_replaced_name }} with --purge" - name: Remove {{ chart_test }} with --purge
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
state: absent state: absent
name: "{{ chart_release_replaced_name }}" name: test-0001
purge: False purge: False
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: install register: install
@@ -272,10 +259,10 @@
that: that:
- install is changed - install is changed
- name: "Install chart again with same name {{ chart_release_replaced_name }}" - name: Install chart again with same name test-0001
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_replaced_name }}" name: test-0001
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -291,7 +278,7 @@
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
state: absent state: absent
name: "{{ chart_release_replaced_name }}" name: test-0001
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
register: install register: install
@@ -303,7 +290,7 @@
- name: "Install {{ chart_test }} from {{ source }} with values_files" - name: "Install {{ chart_test }} from {{ source }} with values_files"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -322,7 +309,7 @@
- name: "Install {{ chart_test }} from {{ source }} with values_files (again)" - name: "Install {{ chart_test }} from {{ source }} with values_files (again)"
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -364,7 +351,7 @@
- name: Release using non-existent context - name: Release using non-existent context
helm: helm:
binary_path: "{{ helm_binary }}" binary_path: "{{ helm_binary }}"
name: "{{ chart_release_name }}" name: test
chart_ref: "{{ chart_source }}" chart_ref: "{{ chart_source }}"
chart_version: "{{ chart_source_version | default(omit) }}" chart_version: "{{ chart_source_version | default(omit) }}"
namespace: "{{ helm_namespace }}" namespace: "{{ helm_namespace }}"
@@ -392,3 +379,5 @@
kind: Namespace kind: Namespace
name: "{{ helm_namespace }}" name: "{{ helm_namespace }}"
state: absent state: absent
wait: true
wait_timeout: 180

View File

@@ -22,8 +22,6 @@
chart_source_upgrade: "/tmp/helm_test_repo_upgrade/stable/{{ chart_test_local_path }}/" chart_source_upgrade: "/tmp/helm_test_repo_upgrade/stable/{{ chart_test_local_path }}/"
chart_test_version: "{{ chart_test_version_local_path }}" chart_test_version: "{{ chart_test_version_local_path }}"
chart_test_version_upgrade: "{{ chart_test_version_upgrade_local_path }}" chart_test_version_upgrade: "{{ chart_test_version_upgrade_local_path }}"
chart_name: "local-path-001"
helm_namespace: "{{ test_namespace[7] }}"
- name: Test appVersion idempotence - name: Test appVersion idempotence
vars: vars:
@@ -68,8 +66,6 @@
source: local_path source: local_path
chart_source: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_app_version }}-{{ chart_test_version }}.tgz" chart_source: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_app_version }}-{{ chart_test_version }}.tgz"
chart_source_upgrade: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_upgrade_app_version }}-{{ chart_test_version_upgrade }}.tgz" chart_source_upgrade: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_upgrade_app_version }}-{{ chart_test_version_upgrade }}.tgz"
chart_name: "local-path-002"
helm_namespace: "{{ test_namespace[8] }}"
- name: Test appVersion handling when null - name: Test appVersion handling when null
vars: vars:
@@ -98,8 +94,6 @@
source: local_path source: local_path
chart_source: "/tmp/helm_test_appversion/test-null/{{ chart_test }}/" chart_source: "/tmp/helm_test_appversion/test-null/{{ chart_test }}/"
chart_source_upgrade: "{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz" chart_source_upgrade: "{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz"
chart_name: "local-path-003"
helm_namespace: "{{ test_namespace[9] }}"
- name: Remove clone repos - name: Remove clone repos
file: file:

View File

@@ -12,7 +12,6 @@
chart_source: "test_helm/{{ chart_test }}" chart_source: "test_helm/{{ chart_test }}"
chart_source_version: "{{ chart_test_version }}" chart_source_version: "{{ chart_test_version }}"
chart_source_version_upgrade: "{{ chart_test_version_upgrade }}" chart_source_version_upgrade: "{{ chart_test_version_upgrade }}"
helm_namespace: "{{ test_namespace[6] }}"
- name: Add chart repo - name: Add chart repo
helm_repository: helm_repository:

View File

@@ -5,4 +5,3 @@
source: url source: url
chart_source: "https://github.com/kubernetes/ingress-nginx/releases/download/{{ chart_test }}-{{ chart_test_version }}/{{ chart_test }}-{{ chart_test_version }}.tgz" chart_source: "https://github.com/kubernetes/ingress-nginx/releases/download/{{ chart_test }}-{{ chart_test_version }}/{{ chart_test }}-{{ chart_test_version }}.tgz"
chart_source_upgrade: "https://github.com/kubernetes/ingress-nginx/releases/download/{{ chart_test }}-{{ chart_test_version_upgrade }}/{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz" chart_source_upgrade: "https://github.com/kubernetes/ingress-nginx/releases/download/{{ chart_test }}-{{ chart_test_version_upgrade }}/{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz"
helm_namespace: "{{ test_namespace[5] }}"

Some files were not shown because too many files have changed in this diff Show More