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
442 changed files with 4329 additions and 26398 deletions

View File

@@ -1,6 +0,0 @@
---
profile: production
exclude_paths:
- tests/integration
- tests/sanity

View File

@@ -1,2 +0,0 @@
# no-changed-when is not requried for examples
plugins/connection/kubectl.py no-changed-when

View File

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

View File

@@ -1,21 +0,0 @@
---
name: Changelog
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
types:
- opened
- reopened
- labeled
- unlabeled
- synchronize
branches:
- main
- stable-*
jobs:
changelog:
uses: ansible-network/github_actions/.github/workflows/changelog.yml@main

View File

@@ -1,14 +0,0 @@
name: galaxy-import
concurrency:
group: ${{ github.head_ref }}
cancel-in-progress: true
on:
pull_request:
branches:
- main
- stable-*
jobs:
galaxy_importer:
uses: ansible-network/github_actions/.github/workflows/galaxy_importer.yml@main

View File

@@ -1,146 +0,0 @@
name: Integration tests
on:
pull_request:
types:
- opened
- reopened
- labeled
- unlabeled
- synchronize
branches:
- main
- stable-*
jobs:
splitter:
env:
source_dir: "./source"
runs-on: ubuntu-latest
outputs:
test_targets: ${{ steps.splitter.outputs.test_targets }}
test_targets_json: ${{ steps.splitter.outputs.test_targets_json }}
test_jobs: ${{ steps.splitter.outputs.test_jobs }}
steps:
- name: Checkout the collection repository
uses: actions/checkout@v3
with:
path: ${{ env.source_dir }}
fetch-depth: "0"
- name: list changes for pull request
id: splitter
uses: ansible-network/github_actions/.github/actions/ansible_test_splitter@main
with:
collections_to_test: ${{ env.source_dir }}
total_jobs: 8
- name: Display splitter output
run: |
echo "test_targets=${{ steps.splitter.outputs.test_targets }}"
echo "test_targets_json=${{ steps.splitter.outputs.test_targets_json }}"
echo "test_jobs=${{ steps.splitter.outputs.test_jobs }}"
shell: bash
integration:
runs-on: ubuntu-latest
timeout-minutes: 60
needs:
- splitter
if: ${{ needs.splitter.outputs.test_targets != '' }}
env:
source: "./source"
cloud_common: "./cloudcommon"
ansible_posix: "./ansible_posix"
strategy:
fail-fast: false
matrix:
ansible-version:
- milestone
python-version:
- "3.12"
enable-turbo-mode:
- true
- false
workflow-id: ${{ fromJson(needs.splitter.outputs.test_jobs) }}
name: "integration-py${{ matrix.python-version }}-${{ matrix.ansible-version }}-${{ matrix.workflow-id }}"
steps:
- name: Read target
id: read-targets
run: |
import json, os
with open(os.environ.get('GITHUB_OUTPUT'), "a", encoding="utf-8") as fh:
fh.write(f'ansible_test_targets={json.loads(os.environ.get("ALL_TEST_TARGETS")).get(os.environ.get("WORKFLOW_ID"))}\n')
shell: python
env:
ALL_TEST_TARGETS: ${{ needs.splitter.outputs.test_targets_json }}
WORKFLOW_ID: ${{ matrix.workflow-id }}
- name: Display ansible test targets
run: |
echo "ansible_test_targets -> ${{ steps.read-targets.outputs.ansible_test_targets }}"
- name: Checkout kubernetes.core repository
uses: actions/checkout@v3
with:
path: ${{ env.source }}
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# install ansible
- name: Install ansible-core (${{ matrix.ansible-version }})
run: >-
python3 -m pip install
https://github.com/ansible/ansible/archive/${{ matrix.ansible-version }}.tar.gz
--disable-pip-version-check
shell: bash
- name: Build and install collection
id: install-src
uses: ansible-network/github_actions/.github/actions/build_install_collection@main
with:
install_python_dependencies: true
source_path: ${{ env.source }}
- name: checkout ansible-collections/cloud.common
uses: ansible-network/github_actions/.github/actions/checkout_dependency@main
with:
repository: ansible-collections/cloud.common
path: ${{ env.cloud_common }}
ref: main
- name: checkout ansible-collections/ansible.posix
uses: ansible-network/github_actions/.github/actions/checkout_dependency@main
with:
repository: ansible-collections/ansible.posix
path: ${{ env.ansible_posix }}
ref: main
- name: install cloud.common collection
uses: ansible-network/github_actions/.github/actions/build_install_collection@main
with:
install_python_dependencies: true
source_path: ${{ env.cloud_common }}
- name: install ansible.posix collection
uses: ansible-network/github_actions/.github/actions/build_install_collection@main
with:
install_python_dependencies: true
source_path: ${{ env.ansible_posix }}
- name: create kubernetes cluster
uses: helm/kind-action@v1.8.0
with:
node_image: "kindest/node:v1.29.2"
- name: Run integration tests
uses: ansible-network/github_actions/.github/actions/ansible_test_integration@main
with:
collection_path: ${{ steps.install-src.outputs.collection_path }}
python_version: ${{ matrix.python-version }}
ansible_version: ${{ matrix.ansible-version }}
ansible_test_targets: ${{ steps.read-targets.outputs.ansible_test_targets }}
ansible_test_environment: |
ENABLE_TURBO_MODE=${{ matrix.enable-turbo-mode }}

View File

@@ -1,24 +0,0 @@
---
name: Linters
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
branches:
- main
- stable-*
tags:
- '*'
jobs:
linters:
uses: ansible-network/github_actions/.github/workflows/tox-linters.yml@main
ansible-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ansible-lint
uses: ansible/ansible-lint@v24.2.3

View File

@@ -1,15 +0,0 @@
---
name: Sanity tests
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
branches:
- main
- stable-*
jobs:
sanity:
uses: ansible-network/github_actions/.github/workflows/sanity.yml@main

View File

@@ -1,14 +0,0 @@
name: Unit tests
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
branches:
- main
- stable-*
jobs:
unit-source:
uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main

1
.gitignore vendored
View File

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

View File

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

View File

@@ -4,316 +4,6 @@ Kubernetes Collection Release Notes
.. contents:: Topics
v5.0.0
======
Release Summary
---------------
This major release drops support for ``ansible-core<2.15``.
Minor Changes
-------------
- inventory/k8s.py - Defer removal of k8s inventory plugin to version 6.0.0 (https://github.com/ansible-collections/kubernetes.core/pull/734).
Breaking Changes / Porting Guide
--------------------------------
- Remove support for ``ansible-core<2.15`` (https://github.com/ansible-collections/kubernetes.core/pull/737).
v4.0.0
======
Release Summary
---------------
This major release brings several bug fixes. We have also removed support for ``ansible-core<2.15`` and deprecated functions and class from ``module_utils/common.py``.
Minor Changes
-------------
- inventory/k8s.py - Defer removal of k8s inventory plugin to version 5.0 (https://github.com/ansible-collections/kubernetes.core/pull/723).
- k8s - The module and K8sService were changed so warnings returned by the K8S API are now displayed to the user.
Removed Features (previously deprecated)
----------------------------------------
- k8s - Support for ``merge_type=json`` has been removed in version 4.0.0. Please use ``kubernetes.core.k8s_json_patch`` instead (https://github.com/ansible-collections/kubernetes.core/pull/722).
- k8s_exec - the previously deprecated ``result.return_code`` return value has been removed, consider using ``result.rc`` instead (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``K8sAnsibleMixin`` class has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``configuration_digest()`` function has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``get_api_client()`` function has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``unique_string()`` function has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
Bugfixes
--------
- Resolve Collections util resource discovery fails when complex subresources present (https://github.com/ansible-collections/kubernetes.core/pull/676).
- align `helmdiff_check()` function commandline rendering with the `deploy()` function (https://github.com/ansible-collections/kubernetes.core/pull/670).
- avoid unsafe conditions in integration tests (https://github.com/ansible-collections/kubernetes.core/pull/665).
- helm - use ``reuse-values`` when running ``helm diff`` command (https://github.com/ansible-collections/kubernetes.core/issues/680).
- integrations test helm_kubeconfig - set helm version to v3.10.3 to avoid incompatability with new bitnami charts (https://github.com/ansible-collections/kubernetes.core/pull/670).
v3.1.0
======
Release Summary
---------------
This release comes with some bugfixes and documentation updates. It also adds new features to the kubectl connection plugin and the kustomize lookup plugin.
Minor Changes
-------------
- kubectl - added support of local enviroment variable that will be used for kubectl and may be requried for establishing connections ifself (https://github.com/ansible-collections/kubernetes.core/pull/702)
- kustomize - new parameter added to --enable-helm (https://github.com/ansible-collections/kubernetes.core/issues/568)
Bugfixes
--------
- helm - expand kubeconfig path with user's home directory for consistency with k8s
- k8s_json_patch - rename action symlink to ensure k8s action plugin is used (https://github.com/ansible-collections/kubernetes.core/pull/652).
v3.0.1
======
Release Summary
---------------
This release fixes issue with resources discovery when complex subresources are present, and fixes issues with `reuse-values` parameter for helm module.
Bugfixes
--------
- Resolve Collections util resource discovery fails when complex subresources present (https://github.com/ansible-collections/kubernetes.core/pull/676).
- align `helmdiff_check()` function commandline rendering with the `deploy()` function (https://github.com/ansible-collections/kubernetes.core/pull/670).
- helm - use ``reuse-values`` when running ``helm diff`` command (https://github.com/ansible-collections/kubernetes.core/issues/680).
- integrations test helm_kubeconfig - set helm version to v3.10.3 to avoid incompatability with new bitnami charts (https://github.com/ansible-collections/kubernetes.core/pull/670).
v3.0.0
======
Release Summary
---------------
This major release drops support for ansible-core versions lower than 2.14, Python versions lower than 3.9 and updates python kubernetes library to 24.2.0, helm/kind-action to 1.8.0, kubernetes >= 1.24, along with bug fixes and minor changes.
Minor Changes
-------------
- helm - add ``reuse_values`` and ``reset_values`` support to helm module (https://github.com/ansible-collections/kubernetes.core/issues/394).
- k8s - add new option ``delete_all`` to support deletion of all resources when state is set to ``absent``. (https://github.com/ansible-collections/kubernetes.core/issues/504)
- k8s, k8s_info - add a hidden_fields option to allow fields to be hidden in the results of k8s and k8s_info
- k8s_drain - add ability to filter the list of pods to be drained by a pod label selector (https://github.com/ansible-collections/kubernetes.core/issues/474).
Breaking Changes / Porting Guide
--------------------------------
- Remove support for ansible-core < 2.14
- Update python kubernetes library to 24.2.0, helm/kind-action to 1.8.0, kubernetes >= 1.24.
Deprecated Features
-------------------
- k8s - the ``k8s`` inventory plugin has been deprecated and will be removed in release 4.0.0 (https://github.com/ansible-collections/kubernetes.core/issues/31).
Bugfixes
--------
- helm - Put the chart_ref into quotes when running ``helm show chart``, ``helm upgrade`` and ``helm dependency update`` commands (https://github.com/ansible-collections/kubernetes.core/issues/653).
- helm - delete temporary file created when deploying chart with option ``release_values`` set (https://github.com/ansible-collections/kubernetes.core/issues/530).
- helm - fix issue occurring when uninstalling chart with statues others than ``deployed`` (https://github.com/ansible-collections/kubernetes.core/issues/319).
- helm - fix post_renderer argument breaking the helm deploy_command (https://github.com/ansible-collections/kubernetes.core/pull/586).
- helm - use post_renderer when checking ``changed`` status for a helm release (https://github.com/ansible-collections/kubernetes.core/pull/588).
- k8s_scale - clean handling of ResourceTimeout exception (https://github.com/ansible-collections/kubernetes.core/issues/583).
- k8s_scale - fix issue when scaling StatefulSets with ``updateStrategy=OnDelete`` (https://github.com/ansible-collections/kubernetes.core/issues/579).
v2.4.0
======
Major Changes
-------------
- refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
Minor Changes
-------------
- Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24 (https://github.com/ansible-collections/kubernetes.core/pull/520).
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
- added ignore.txt for Ansible 2.14 devel branch.
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
- helm - add support for -set-file, -set-json, -set and -set-string options when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
- helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
- helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
- helm - add support for timeout cli parameter to allow setting Helm timeout independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
- helm - when ansible is executed in check mode, return the diff between what's deployed and what will be deployed.
- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
- helm_info - add hooks, notes and manifest as part of returned information (https://github.com/ansible-collections/kubernetes.core/pull/546).
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
- helm_info - added possibility to get all values by adding get_all_values parameter (https://github.com/ansible-collections/kubernetes.core/pull/531).
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
- helm_plugin - Add support for helm plugin update using state=update.
- helm_repository - Ability to replace (overwrite) the repo if it already exists by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``, and ``ca_cert``.
- helm_repository - mark `pass_credentials` as no_log=True to silence false warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
- helm_template - add name (NAME of release) and disable_hook as optional module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
- helm_template - add support for -set-file, -set-json, -set and -set-string options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
- k8s, k8s_scale, k8s_service - add support for resource definition as manifest via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
- k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod (https://github.com/ansible-collections/kubernetes.core/issues/486).
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
- k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
- k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
- k8s_log - add the ``all_containers`` for retrieving all containers' logs in the pod(s).
- k8s_log - added the `previous` parameter for retrieving the previously terminated pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
- k8s_log - added the `tail_lines` parameter to limit the number of lines to be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
Bugfixes
--------
- Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to the client, add conditional check on kubernetes client version as this feature is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
- Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
- Helm - Fix issue with alternative kubeconfig provided with validate_certs=False (https://github.com/ansible-collections/kubernetes.core/issues/538).
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
- add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name (https://github.com/ansible-collections/kubernetes.core/issues/558).
- common - Ensure the label_selectors parameter of _wait_for method is optional.
- common - handle ``aliases`` passed from inventory and lookup plugins.
- helm_template - evaluate release_values after values_files, insuring highest precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
- import exception from ``kubernetes.client.rest``.
- k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
- k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
- k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
- k8s_info - fix issue when module returns successful true after the resource cache has been established during periods where communication to the api-server is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
- k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
- k8s_log - fix exception raised when the name is not provided for resources requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
- module_utils/k8s/client.py - fix issue when trying to authenticate with host, client_cert and client_key parameters only.
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
New Modules
-----------
- helm_pull - download a chart from a repository and (optionally) unpack it in local directory.
v2.3.1
======
Bugfixes
--------
- Catch expectation raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
- Remove `omit` placeholder when defining resource using template parameter (https://github.com/ansible-collections/kubernetes.core/issues/431).
- k8s - fix the issue when trying to delete resources using label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/433).
- k8s_cp - fix issue when using parameter local_path with file on managed node. (https://github.com/ansible-collections/kubernetes.core/issues/421).
- k8s_drain - fix error occurring when trying to drain node with disable_eviction set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
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
======

View File

@@ -1,8 +1,8 @@
# Also needs to be updated in galaxy.yml
VERSION = 5.0.0
VERSION = 2.1.1
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:
rm -f kubernetes-core-${VERSION}.tar.gz
@@ -22,7 +22,10 @@ test-sanity:
ansible-test sanity --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
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:
ansible-test units --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)

View File

@@ -1,48 +0,0 @@
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.

View File

@@ -2,17 +2,15 @@
[![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.
<!--start requires_ansible-->
## Ansible version compatibility
This collection has been tested against following Ansible versions: **>=2.15.0**.
This collection has been tested against following Ansible versions: **>=2.9.17**.
For collections that support Ansible 2.9, please ensure you update your `network_os` to use the
fully qualified collection name (for example, `cisco.ios.ios`).
Plugins and modules within a collection may be tested with only specific Ansible versions.
A collection may contain metadata that identifies these versions.
PEP440 is the schema used to describe the versions of Ansible.
@@ -20,14 +18,10 @@ PEP440 is the schema used to describe the versions of Ansible.
## Python Support
* Collection supports 3.9+
* Collection supports 3.6+
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.24.
## Included content
Click on the name of a plugin or module to view that content's documentation:
@@ -52,7 +46,6 @@ 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.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
Name | Description
@@ -61,13 +54,10 @@ Name | Description
[kubernetes.core.helm_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_info_module.rst)|Get information from Helm package deployed inside the cluster
[kubernetes.core.helm_plugin](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_module.rst)|Manage Helm plugins
[kubernetes.core.helm_plugin_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_info_module.rst)|Gather information about Helm plugins
[kubernetes.core.helm_pull](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_pull_module.rst)|download a chart from a repository and (optionally) unpack it in local directory.
[kubernetes.core.helm_repository](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_repository_module.rst)|Manage Helm repositories.
[kubernetes.core.helm_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_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_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
@@ -75,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_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_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-->
@@ -93,7 +82,7 @@ You can also include it in a `requirements.yml` file and install it via `ansible
---
collections:
- name: kubernetes.core
version: 5.0.0
version: 2.1.1
```
### Installing the Kubernetes Python Library
@@ -170,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.
## 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
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,3 +1 @@
kubernetes-client [platform:fedora]
openshift-clients [platform:rhel-8]
openshift-clients [platform:rhel-9]

View File

@@ -429,483 +429,3 @@ releases:
fragments:
- 151-check-auth-params-for-existence.yaml
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'
2.3.1:
changes:
bugfixes:
- Catch expectation raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
- Remove `omit` placeholder when defining resource using template parameter
(https://github.com/ansible-collections/kubernetes.core/issues/431).
- k8s - fix the issue when trying to delete resources using label_selectors
options (https://github.com/ansible-collections/kubernetes.core/issues/433).
- k8s_cp - fix issue when using parameter local_path with file on managed node.
(https://github.com/ansible-collections/kubernetes.core/issues/421).
- k8s_drain - fix error occurring when trying to drain node with disable_eviction
set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
fragments:
- 408-fix-wait-on-exception.yml
- 417-fix-k8s-drain-delete-options.yaml
- 422-k8s_cp-fix-issue-when-issue-local_path.yaml
- 432-fix-issue-when-using-template-parameter.yaml
- 434-fix-k8s-delete-using-label_selector.yaml
release_date: '2022-05-02'
2.4.0:
changes:
bugfixes:
- Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to
the client, add conditional check on kubernetes client version as this feature
is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
- Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
- Helm - Fix issue with alternative kubeconfig provided with validate_certs=False
(https://github.com/ansible-collections/kubernetes.core/issues/538).
- Various modules and plugins - use vendored version of ``distutils.version``
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
- add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name
(https://github.com/ansible-collections/kubernetes.core/issues/558).
- common - Ensure the label_selectors parameter of _wait_for method is optional.
- common - handle ``aliases`` passed from inventory and lookup plugins.
- helm_template - evaluate release_values after values_files, insuring highest
precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
- import exception from ``kubernetes.client.rest``.
- k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
- k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
- k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
- k8s_drain - fix error caused by accessing an undefined variable when pods
have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
- k8s_info - fix issue when module returns successful true after the resource
cache has been established during periods where communication to the api-server
is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
- k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
- k8s_log - fix exception raised when the name is not provided for resources
requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
- module_utils.common - change default opening mode to read-bytes to avoid bad
interpretation of non ascii characters and strings, often present in 3rd party
manifests.
- module_utils/k8s/client.py - fix issue when trying to authenticate with host,
client_cert and client_key parameters only.
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
major_changes:
- refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
minor_changes:
- Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24
(https://github.com/ansible-collections/kubernetes.core/pull/520).
- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
- added ignore.txt for Ansible 2.14 devel branch.
- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
- helm - add support for -set-file, -set-json, -set and -set-string options
when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
- helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
- helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
- helm - add support for timeout cli parameter to allow setting Helm timeout
independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
- helm - when ansible is executed in check mode, return the diff between what's
deployed and what will be deployed.
- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for
in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
- helm_info - add hooks, notes and manifest as part of returned information
(https://github.com/ansible-collections/kubernetes.core/pull/546).
- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
- helm_info - added possibility to get all values by adding get_all_values parameter
(https://github.com/ansible-collections/kubernetes.core/pull/531).
- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
- helm_plugin - Add support for helm plugin update using state=update.
- helm_repository - Ability to replace (overwrite) the repo if it already exists
by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``,
and ``ca_cert``.
- helm_repository - mark `pass_credentials` as no_log=True to silence false
warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
- helm_template - add name (NAME of release) and disable_hook as optional module
arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
- helm_template - add support for -set-file, -set-json, -set and -set-string
options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
- k8s, k8s_scale, k8s_service - add support for resource definition as manifest
via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
- k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod
(https://github.com/ansible-collections/kubernetes.core/issues/486).
- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options``
to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
- k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
- k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
- k8s_log - add the ``all_containers`` for retrieving all containers' logs in
the pod(s).
- k8s_log - added the `previous` parameter for retrieving the previously terminated
pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
- k8s_log - added the `tail_lines` parameter to limit the number of lines to
be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in
the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
fragments:
- 0-copy_ignore_txt.yml
- 208-add-dependency-update.yaml
- 226-add-version-parameter-to-helm_plugin.yml
- 231-helm-add-timeout-parameter.yaml
- 238-helm-add-support-for-helm-uninstall-wait.yaml
- 238-k8s-add-support-for-generate_name.yml
- 245-add-dry-run.yaml
- 250-k8s-add-support-for-impersonation.yaml
- 253-dont-wait-on-list-resources.yaml
- 255-k8s_scale-k8s_rollback-add-support-for-check_mode.yml
- 260-k8s-add-support-for-server_side_apply.yml
- 272-k8s-add-support-no_proxy.yaml
- 282-helm-repository-add-pass-credentials.yaml
- 290-returns-diff-in-check-mode.yaml
- 295-fix-k8s-drain-variable-declaration.yaml
- 298-remove-binary-file.yaml
- 30-helm-add-post-renderer-support.yml
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
- 313-helm-template-add-support-for-name-and-disablehook.yml
- 313-helm-template-add-support-for-show-only-and-release-namespace.yml
- 321-kubectl_sleep.yml
- 322-Add-delete_emptydir_data-to-drain-delete_options.yaml
- 335-k8s-lookup-add-support-for-turbo-mode.yml
- 347-routing.yml
- 348-helm_template-fix-precedence-of-release-values-over-values-files.yaml
- 358-k8s_exec.yml
- 364-use-resource-prefix.yaml
- 377-helm-info-state.yml
- 389-helm-add-support-chart_repo_url-on-helm_diff.yml
- 391-fix-statefulset-wait.yaml
- 411_k8s_json_patch.yml
- 412_pass_creds.yml
- 417_deprecation.yml
- 428-fix-kubeconfig-parameter-with-multiple-config-files.yaml
- 437-k8s-add-support-for-previous-logs.yaml
- 456-replace-distutils.yml
- 478-add-support-for-manifest-url.yaml
- 481-refactor-common.yml
- 488-add-support-for-tail-logs.yaml
- 493-k8s_log-fix-module-when-pod-does-exist.yaml
- 497-helm-add-support-for-in-memory-kubeconfig.yml
- 498-k8s-honor-aliases.yaml
- 505-add-from-yaml-all-example.yml
- 509-helm-repo-add-force_update-argument.yaml
- 512-k8s_cp-add-support-for-check_mode-update-command-for-listing-files-into-pod.yaml
- 515-update-sanity-for-2-15.yml
- 522-fix-helm-tests.yml
- 523-helm_info-get-all-values.yaml
- 528-k8s_log-support-all_containers-options.yml
- 532-k8s_crd-fix-integration-test.yml
- 546-helm-install-add-support-for-set-options.yaml
- 549-fix-server-side-apply.yaml
- 552-k8s_cp-fix-issue-when-copying-item-with-space-in-its-name.yml
- 561-fix-dry-run.yml
- 562-helm-fix-issue-when-alternative-kubeconfig-is-provided.yaml
- 571-k8s_info-fix-issue-with-api-server.yaml
- _wait_for_label_selector_optional.yaml
- disutils.version.yml
- exception.yml
- fix-ci-unit-tests.yaml
- helm_repository.yml
- ignore_2.14.yml
- k8s_config_resource_name-add-missing-documentation.yml
- k8s_rollback_reduce_tmeouts.yaml
- k8s_user_impersonation_k8s_1_24.yaml
- minor-tests-duration.yaml
modules:
- description: download a chart from a repository and (optionally) unpack it in
local directory.
name: helm_pull
namespace: ''
release_date: '2023-01-24'
3.0.0:
changes:
breaking_changes:
- Remove support for ansible-core < 2.14
- Update python kubernetes library to 24.2.0, helm/kind-action to 1.8.0, kubernetes
>= 1.24.
bugfixes:
- helm - Put the chart_ref into quotes when running ``helm show chart``, ``helm
upgrade`` and ``helm dependency update`` commands (https://github.com/ansible-collections/kubernetes.core/issues/653).
- helm - delete temporary file created when deploying chart with option ``release_values``
set (https://github.com/ansible-collections/kubernetes.core/issues/530).
- helm - fix issue occurring when uninstalling chart with statues others than
``deployed`` (https://github.com/ansible-collections/kubernetes.core/issues/319).
- helm - fix post_renderer argument breaking the helm deploy_command (https://github.com/ansible-collections/kubernetes.core/pull/586).
- helm - use post_renderer when checking ``changed`` status for a helm release
(https://github.com/ansible-collections/kubernetes.core/pull/588).
- k8s_scale - clean handling of ResourceTimeout exception (https://github.com/ansible-collections/kubernetes.core/issues/583).
- k8s_scale - fix issue when scaling StatefulSets with ``updateStrategy=OnDelete``
(https://github.com/ansible-collections/kubernetes.core/issues/579).
deprecated_features:
- k8s - the ``k8s`` inventory plugin has been deprecated and will be removed
in release 4.0.0 (https://github.com/ansible-collections/kubernetes.core/issues/31).
minor_changes:
- helm - add ``reuse_values`` and ``reset_values`` support to helm module (https://github.com/ansible-collections/kubernetes.core/issues/394).
- k8s - add new option ``delete_all`` to support deletion of all resources when
state is set to ``absent``. (https://github.com/ansible-collections/kubernetes.core/issues/504)
- k8s, k8s_info - add a hidden_fields option to allow fields to be hidden in
the results of k8s and k8s_info
- k8s_drain - add ability to filter the list of pods to be drained by a pod
label selector (https://github.com/ansible-collections/kubernetes.core/issues/474).
release_summary: This major release drops support for ansible-core versions
lower than 2.14, Python versions lower than 3.9 and updates python kubernetes
library to 24.2.0, helm/kind-action to 1.8.0, kubernetes >= 1.24, along with
bug fixes and minor changes.
fragments:
- 20230206-deprecate-k8s-inventory.yml
- 20231110-helm-quote-ref.yaml
- 517-k8s-make-name-optional.yaml
- 575-helm-add-support-for-reuse_values-and-reset_values.yml
- 579-k8s_scale-fix-issue-with-scaling-statefulsets.yml
- 583-k8s_scale-clean-handling-of-ResourceTimeout-exception.yaml
- 586-helm-fix-post-renderer-arg.yml
- 588-helm-use-post-renderer-for-helmdiff.yml
- 589-helm-uninstall-chart-releases-with-statuses-different-than-deployed.yaml
- 606-k8s_drain-add-pod_selectors-parameter.yaml
- 612-fix-helm-tests.yaml
- 629-add-hidden-fields-option.yaml
- gha-sanity-fixes.yaml
- helm-delete-temporary-file-created-when-using-option-release_values.yaml
- remove_ansible_2_13.yaml
- update_supported_versions.yaml
release_date: '2023-11-17'
3.0.1:
changes:
bugfixes:
- Resolve Collections util resource discovery fails when complex subresources
present (https://github.com/ansible-collections/kubernetes.core/pull/676).
- align `helmdiff_check()` function commandline rendering with the `deploy()`
function (https://github.com/ansible-collections/kubernetes.core/pull/670).
- helm - use ``reuse-values`` when running ``helm diff`` command (https://github.com/ansible-collections/kubernetes.core/issues/680).
- integrations test helm_kubeconfig - set helm version to v3.10.3 to avoid incompatability
with new bitnami charts (https://github.com/ansible-collections/kubernetes.core/pull/670).
release_summary: This release fixes issue with resources discovery when complex
subresources are present, and fixes issues with `reuse-values` parameter for
helm module.
fragments:
- 20240117-fix-helm-diff-cmd-line-rendering.yml
- 20240222-Collections-util-resource-discovery-fails-when-complex-subresources-present.yml
- 20240228-fix-helm-diff-with-reuse-values.yml
- 3.0.1.yml
release_date: '2024-03-01'
3.1.0:
changes:
bugfixes:
- helm - expand kubeconfig path with user's home directory for consistency with
k8s
- k8s_json_patch - rename action symlink to ensure k8s action plugin is used
(https://github.com/ansible-collections/kubernetes.core/pull/652).
minor_changes:
- kubectl - added support of local enviroment variable that will be used for
kubectl and may be requried for establishing connections ifself (https://github.com/ansible-collections/kubernetes.core/pull/702)
- kustomize - new parameter added to --enable-helm (https://github.com/ansible-collections/kubernetes.core/issues/568)
release_summary: This release comes with some bugfixes and documentation updates.
It also adds new features to the kubectl connection plugin and the kustomize
lookup plugin.
fragments:
- 20240426-add-support-of-kubectl-local-env-vars-for-connection-plugin.yml
- 3.1.0.yml
- 592-kustomize-helm-support.yml
- 652-fix-json-patch-action.yml
- 654-helm-expand-user.yml
release_date: '2024-05-16'
4.0.0:
changes:
bugfixes:
- Resolve Collections util resource discovery fails when complex subresources
present (https://github.com/ansible-collections/kubernetes.core/pull/676).
- align `helmdiff_check()` function commandline rendering with the `deploy()`
function (https://github.com/ansible-collections/kubernetes.core/pull/670).
- avoid unsafe conditions in integration tests (https://github.com/ansible-collections/kubernetes.core/pull/665).
- helm - use ``reuse-values`` when running ``helm diff`` command (https://github.com/ansible-collections/kubernetes.core/issues/680).
- integrations test helm_kubeconfig - set helm version to v3.10.3 to avoid incompatability
with new bitnami charts (https://github.com/ansible-collections/kubernetes.core/pull/670).
minor_changes:
- inventory/k8s.py - Defer removal of k8s inventory plugin to version 5.0 (https://github.com/ansible-collections/kubernetes.core/pull/723).
- k8s - The module and K8sService were changed so warnings returned by the K8S
API are now displayed to the user.
release_summary: This major release brings several bug fixes. We have also removed
support for ``ansible-core<2.15`` and deprecated functions and class from
``module_utils/common.py``.
removed_features:
- k8s - Support for ``merge_type=json`` has been removed in version 4.0.0. Please
use ``kubernetes.core.k8s_json_patch`` instead (https://github.com/ansible-collections/kubernetes.core/pull/722).
- k8s_exec - the previously deprecated ``result.return_code`` return value has
been removed, consider using ``result.rc`` instead (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``K8sAnsibleMixin`` class
has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``configuration_digest()``
function has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``get_api_client()`` function
has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
- module_utils/common.py - the previously deprecated ``unique_string()`` function
has been removed (https://github.com/ansible-collections/kubernetes.core/pull/726).
fragments:
- 20231206-fix-unsafe-condition-in-integration.yml
- 20240117-fix-helm-diff-cmd-line-rendering.yml
- 20240222-Collections-util-resource-discovery-fails-when-complex-subresources-present.yml
- 20240228-fix-helm-diff-with-reuse-values.yml
- 20240423-k8s-display-warnings-to-users.yml
- 4.0.0.yaml
- inventory-update_removal_date.yml
- k8s-merge_type-removed.yml
- module_utils-common-remove-deprecated-functions-and-class.yaml
release_date: '2024-05-24'
5.0.0:
changes:
breaking_changes:
- Remove support for ``ansible-core<2.15`` (https://github.com/ansible-collections/kubernetes.core/pull/737).
minor_changes:
- inventory/k8s.py - Defer removal of k8s inventory plugin to version 6.0.0
(https://github.com/ansible-collections/kubernetes.core/pull/734).
release_summary: This major release drops support for ``ansible-core<2.15``.
fragments:
- 20240530-ansible-core-support-update.yaml
- 20240530-defer-removal-and-ansible-core-support-update.yaml
- 5.0.0.yml
release_date: '2024-05-31'

View File

@@ -1,3 +0,0 @@
---
minor_changes:
- connection/kubectl.py - Added an example of using the kubectl connection plugin to the documentation (https://github.com/ansible-collections/kubernetes.core/pull/741).

View File

@@ -15,7 +15,7 @@ Following document provides overview of Ansible Turbo mode in ``kubernetes.core`
Synopsis
--------
- 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
------------
@@ -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)
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
------------
@@ -99,7 +90,7 @@ The background service
The daemon kills itself after 15s, and communication are done
through an Unix socket.
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
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

@@ -105,27 +105,6 @@ Parameters
<div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>get_all_values</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.4.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>Set to <code>True</code> if you want to get all (computed) values of the release.</div>
<div>When <code>False</code> (default), only user supplied values are returned.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -148,7 +127,7 @@ Parameters
<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>
<span style="color: purple">path</span>
</div>
</td>
<td>
@@ -156,7 +135,6 @@ Parameters
<td>
<div>Helm option to specify kubeconfig path to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
<div>The configuration can be provided as dictionary. Added in version 2.4.0.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
</td>
</tr>
@@ -194,32 +172,6 @@ Parameters
<div style="font-size: small; color: darkgreen"><br/>aliases: namespace</div>
</td>
</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>
<b>Default:</b><br/><div style="color: blue">[]</div>
</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>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -252,18 +204,11 @@ Examples
.. 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:
name: test
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
@@ -325,42 +270,6 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>hooks</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=dictionary</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>always</td>
<td>
<div>Hooks of the release</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>manifest</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=dictionary</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>always</td>
<td>
<div>Manifest of the release</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
@@ -393,23 +302,6 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>notes</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>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>always</td>
<td>
<div>Notes of the release</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">

View File

@@ -36,12 +36,12 @@ Parameters
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="2">Parameter</th>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>api_key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -57,7 +57,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>atomic</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -76,7 +76,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>binary_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -91,7 +91,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>ca_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -108,7 +108,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ref</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -127,7 +127,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_repo_url</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -142,7 +142,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_version</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -157,7 +157,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>context</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -174,7 +174,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>create_namespace</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -194,32 +194,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>dependency_update</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.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>Run standalone <code>helm dependency update CHART</code> before the operation.</div>
<div>Run inline <code>--dependency-update</code> with <code>helm install</code> command. This feature is not supported yet with the <code>helm upgrade</code> command.</div>
<div>So we should consider to use <em>dependency_update</em> options with <em>replace</em> option enabled when specifying <em>chart_repo_url</em>.</div>
<div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
<div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
<div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>disable_hook</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -238,7 +213,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>force</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -257,24 +232,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<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>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>host</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -290,12 +248,12 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<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>
<span style="color: purple">path</span>
</div>
</td>
<td>
@@ -303,28 +261,11 @@ Parameters
<td>
<div>Helm option to specify kubeconfig path to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
<div>The configuration can be provided as dictionary. Added in version 2.4.0.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>post_renderer</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.4.0</div>
</td>
<td>
</td>
<td>
<div>Path to an executable to be used for post rendering.</div>
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>purge</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -343,7 +284,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_name</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -360,7 +301,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_namespace</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -377,7 +318,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_state</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -397,7 +338,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_values</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -414,7 +355,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>replace</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -432,112 +373,10 @@ Parameters
<td>
<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>mutually exclusive with with <code>history_max</code>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>reset_values</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.5.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>When upgrading package, reset the values to the ones built into the chart.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>reuse_values</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.5.0</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>When upgrading package, specifies wether to reuse the last release&#x27;s values and merge in any overrides from parameters <em>release_values</em>, <em>values_files</em> or <em>set_values</em>.</div>
<div>If <em>reset_values</em> is set to <code>True</code>, this is ignored.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>set_values</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>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>
</td>
<td>
<div>Values to pass to chart configuration</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>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>Value to pass to chart configuration (e.g phase=prod).</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>value_type</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>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>raw</b>&nbsp;&larr;</div></li>
<li>string</li>
<li>json</li>
<li>file</li>
</ul>
</td>
<td>
<div>Use <code>raw</code> set individual value.</div>
<div>Use <code>string</code> to force a string for an individual value.</div>
<div>Use <code>file</code> to set individual values from a file when the value itself is too long for the command line or is dynamically generated.</div>
<div>Use <code>json</code> to set json values (scalars/objects/arrays). This feature requires helm&gt;=3.10.0.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>skip_crds</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -557,25 +396,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<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>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>update_repo_cache</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -590,11 +411,11 @@ Parameters
</ul>
</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>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>validate_certs</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -615,7 +436,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>values_files</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -636,7 +457,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -651,12 +472,11 @@ Parameters
</ul>
</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>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>
<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>
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait_timeout</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -668,19 +488,12 @@ Parameters
</td>
<td>
<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>
</tr>
</table>
<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
@@ -730,22 +543,6 @@ Examples
state: absent
wait: true
- name: Separately update the repository cache
kubernetes.core.helm:
name: dummy
namespace: kube-system
state: absent
update_repo_cache: true
- name: Deploy Grafana chart using set values on target
kubernetes.core.helm:
name: test
chart_ref: stable/grafana
release_namespace: monitoring
set_values:
- value: phase=prod
value_type: string
# From git
- name: Git clone stable repo on HEAD
ansible.builtin.git:
@@ -791,17 +588,6 @@ Examples
logging:
enabled: True
# Deploy latest version
- name: Deploy latest version of Grafana chart using reuse_values
kubernetes.core.helm:
name: test
chart_ref: stable/grafana
release_namespace: monitoring
reuse_values: true
values:
replicas: 2
version: 3e8ec0b2dffa40fb97d5342e4af887de95faa8c61a62480dd7f8aa03dffcf533
Return Values

View File

@@ -126,7 +126,7 @@ Parameters
<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>
<span style="color: purple">path</span>
</div>
</td>
<td>
@@ -134,7 +134,6 @@ Parameters
<td>
<div>Helm option to specify kubeconfig path to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
<div>The configuration can be provided as dictionary. Added in version 2.4.0.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
</td>
</tr>

View File

@@ -126,7 +126,7 @@ Parameters
<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>
<span style="color: purple">path</span>
</div>
</td>
<td>
@@ -134,7 +134,6 @@ Parameters
<td>
<div>Helm option to specify kubeconfig path to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
<div>The configuration can be provided as dictionary. Added in version 2.4.0.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
</td>
</tr>
@@ -151,7 +150,7 @@ Parameters
</td>
<td>
<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>
</tr>
<tr>
@@ -171,23 +170,6 @@ Parameters
<div>Required only if <code>state=present</code>.</div>
</td>
</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>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -201,12 +183,10 @@ Parameters
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>absent</li>
<li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
<li>latest</li>
</ul>
</td>
<td>
<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>
</td>
</tr>
@@ -257,17 +237,6 @@ Examples
plugin_name: env
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

View File

@@ -1,467 +0,0 @@
.. _kubernetes.core.helm_pull_module:
*************************
kubernetes.core.helm_pull
*************************
**download a chart from a repository and (optionally) unpack it in local directory.**
Version added: 2.4.0
.. contents::
:local:
:depth: 1
Synopsis
--------
- Retrieve a package from a package repository, and download it locally.
- It can also be used to perform cryptographic verification of a chart without installing the chart.
- There are options for unpacking the chart after download.
Requirements
------------
The below requirements are needed on the host that executes this module.
- helm >= 3.0 (https://github.com/helm/helm/releases)
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>binary_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>The path of a helm binary to use.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ca_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Verify certificates of HTTPS-enabled servers using this CA bundle.</div>
<div>Requires helm &gt;= 3.1.0.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_devel</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Use development versions, too. Equivalent to version &#x27;&gt;0.0.0-0&#x27;.</div>
<div>Mutually exclusive with <code>chart_version</code>.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ref</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>chart name on chart repository.</div>
<div>absolute URL.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ssl_cert_file</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Identify HTTPS client using this SSL certificate file.</div>
<div>Requires helm &gt;= 3.1.0.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ssl_key_file</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Identify HTTPS client using this SSL key file</div>
<div>Requires helm &gt;= 3.1.0.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_version</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify a version constraint for the chart version to use.</div>
<div>This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).</div>
<div>Mutually exclusive with <code>chart_devel</code>.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>destination</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>location to write the chart.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>pass_credentials</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Pass credentials to all domains.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>provenance</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Fetch the provenance file, but don&#x27;t perform verification.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>repo_password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Chart repository password where to locate the requested chart.</div>
<div>Required if <code>repo_username</code> is specified.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: password, chart_repo_password</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>repo_url</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>chart repository url where to locate the requested chart.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: url, chart_repo_url</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>repo_username</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Chart repository username where to locate the requested chart.</div>
<div>Required if <code>repo_password</code> is specified.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: username, chart_repo_username</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>skip_tls_certs_check</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Whether or not to check tls certificate for the chart download.</div>
<div>Requires helm &gt;= 3.3.0.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>untar_chart</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>if set to true, will untar the chart after downloading it.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>verify_chart</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Verify the package before using it.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>verify_chart_keyring</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>location of public keys used for verification.</div>
</td>
</tr>
</table>
<br/>
Examples
--------
.. code-block:: yaml
- name: Download chart using chart url
kubernetes.core.helm_pull:
chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
destination: /path/to/chart
- name: Download Chart using chart_name and repo_url
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
- name: Download Chart (skip tls certificate check)
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
skip_tls_certs_check: yes
- name: Download Chart using chart registry credentials
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
username: myuser
password: mypassword123
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>command</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>always</td>
<td>
<div>Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
<br/>
<div style="font-size: smaller"><b>Sample:</b></div>
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm pull --repo test ...</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>rc</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>always</td>
<td>
<div>Helm pull command return code</div>
<br/>
<div style="font-size: smaller"><b>Sample:</b></div>
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">1</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>stderr</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>always</td>
<td>
<div>Full `helm pull` command stderr, in case you want to display it or examine the event log</div>
<br/>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>stdout</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>always</td>
<td>
<div>Full `helm pull` command stdout, in case you want to display it or examine the event log</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)

View File

@@ -40,22 +40,6 @@ Parameters
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>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>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -71,117 +55,6 @@ Parameters
<div>The path of a helm binary to use.</div>
</td>
</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>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>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>
</td>
<td>
<div>Helm option to specify which kubeconfig context to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_CONTEXT</code> will be used instead.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>force_update</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.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>Whether or not to replace (overwrite) the repo if it already exists.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: force</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>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>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>
</td>
<td>
<div>Helm option to specify kubeconfig path to use.</div>
<div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
<div>The configuration can be provided as dictionary.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</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>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -269,27 +142,6 @@ Parameters
<div style="font-size: small; color: darkgreen"><br/>aliases: username</div>
</td>
</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>
<br/>

View File

@@ -28,12 +28,12 @@ Parameters
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="2">Parameter</th>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>binary_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -48,7 +48,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_ref</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -67,7 +67,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_repo_url</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -82,7 +82,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>chart_version</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -97,50 +97,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>dependency_update</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.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>Run helm dependency update before the operation.</div>
<div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
<div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
<div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>disable_hook</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.4.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>Prevent hooks from running during install.</div>
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>include_crds</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -159,7 +116,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>output_dir</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -175,40 +132,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_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.4.0</div>
</td>
<td>
</td>
<td>
<div>Release name to use in rendered templates.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: name</div>
</td>
</tr>
<tr>
<td colspan="2">
<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.4.0</div>
</td>
<td>
</td>
<td>
<div>namespace scope for this request.</div>
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>release_values</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -225,85 +149,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>set_values</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>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.4.0</div>
</td>
<td>
</td>
<td>
<div>Values to pass to chart configuration.</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>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>Value to pass to chart configuration (e.g phase=prod).</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>value_type</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>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>raw</b>&nbsp;&larr;</div></li>
<li>string</li>
<li>json</li>
<li>file</li>
</ul>
</td>
<td>
<div>Use <code>raw</code> set individual value.</div>
<div>Use <code>string</code> to force a string for an individual value.</div>
<div>Use <code>file</code> to set individual values from a file when the value itself is too long for the command line or is dynamically generated.</div>
<div>Use <code>json</code> to set json values (scalars/objects/arrays). This feature requires helm&gt;=3.10.0.</div>
</td>
</tr>
<tr>
<td colspan="2">
<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.4.0</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>Only show manifests rendered from the given templates.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>update_repo_cache</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -322,7 +168,7 @@ Parameters
</td>
</tr>
<tr>
<td colspan="2">
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>values_files</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -367,24 +213,6 @@ Examples
dest: myfile.yaml
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

View File

@@ -27,8 +27,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -136,41 +136,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -196,34 +161,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
</tr>
<tr>

View File

@@ -1,609 +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.9
- kubernetes >= 24.2.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: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
<div>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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 content into a file in the remote pod
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,665 +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.9
- kubernetes >= 24.2.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>
<b>Default:</b><br/><div style="color: blue">{}</div>
</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: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
<div>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>pod_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.5.0</div>
</td>
<td>
</td>
<td>
<div>Label selector to filter pods on the node.</div>
<div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: label_selectors</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
- name: Drain node "foo" using label selector to filter the list of pods to be drained.
kubernetes.core.k8s_drain:
state: drain
name: foo
pod_selectors:
- 'app!=csi-attacher'
- 'app!=csi-provisioner'
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

@@ -25,8 +25,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -117,7 +117,7 @@ Parameters
<td>
</td>
<td>
<div>The command to execute.</div>
<div>The command to execute</div>
</td>
</tr>
<tr>
@@ -134,7 +134,6 @@ Parameters
<td>
<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>If not specified, will choose the first container from the given pod as kubectl cmdline does.</div>
</td>
</tr>
<tr>
@@ -167,56 +166,19 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -232,26 +194,7 @@ Parameters
<td>
</td>
<td>
<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>
<div>The pod namespace name</div>
</td>
</tr>
<tr>
@@ -306,7 +249,7 @@ Parameters
<td>
</td>
<td>
<div>The pod name.</div>
<div>The pod name</div>
</td>
</tr>
<tr>
@@ -323,7 +266,7 @@ Parameters
<td>
<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>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>
</tr>
<tr>
@@ -439,7 +382,6 @@ Notes
-----
.. 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.
- 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.
@@ -468,14 +410,7 @@ Examples
- name: Check last command status
debug:
msg: "cmd failed"
when: command_status.rc != 0
- name: Specify a container name to execute the command on
kubernetes.core.k8s_exec:
namespace: myproject
pod: busybox-test
container: manager
command: echo "hello"
when: command_status.return_code != 0
@@ -508,23 +443,6 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
</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>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>return_code</b>
@@ -535,7 +453,7 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
</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/>
</td>
</tr>

View File

@@ -28,8 +28,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -153,31 +153,11 @@ Parameters
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of field selectors to use to filter results</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>hidden_fields</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.5.0</div>
</td>
<td>
</td>
<td>
<div>Hide fields matching any of the field definitions in the result</div>
<div>An example might be <code>hidden_fields=[metadata.managedFields]</code></div>
<div>Only field definitions that don&#x27;t reference list items are supported (so V(spec.containers[0]) would not work)</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -193,41 +173,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -253,15 +198,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -275,7 +218,6 @@ Parameters
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of label selectors to use to filter results</div>
@@ -317,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>
</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>

View File

@@ -13,15 +13,6 @@ kubernetes.core.k8s
:local:
:depth: 1
DEPRECATED
----------
:Removed in collection release after
:Why: As discussed in https://github.com/ansible-collections/kubernetes.core/issues/31, we decided to
remove the k8s inventory plugin in release 6.0.0.
:Alternative: Use :ref:`kubernetes.core.k8s_info <kubernetes.core.k8s_info_module>` and :ref:`ansible.builtin.add_host <ansible.builtin.add_host_module>` instead.
Synopsis
--------
@@ -36,8 +27,8 @@ Requirements
------------
The below requirements are needed on the local Ansible controller node that executes this inventory.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -331,24 +322,24 @@ Examples
# File must be named k8s.yaml or k8s.yml
- name: Authenticate with token, and return all pods and services for all namespaces
plugin: kubernetes.core.k8s
connections:
- host: https://192.168.64.4:8443
api_key: xxxxxxxxxxxxxxxx
validate_certs: false
# Authenticate with token, and return all pods and services for all namespaces
plugin: kubernetes.core.k8s
connections:
- host: https://192.168.64.4:8443
api_key: xxxxxxxxxxxxxxxx
validate_certs: false
- name: Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
plugin: kubernetes.core.k8s
connections:
- namespaces:
- testing
# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
plugin: kubernetes.core.k8s
connections:
- namespaces:
- testing
- name: Use a custom config file, and a specific context.
plugin: kubernetes.core.k8s
connections:
- kubeconfig: /path/to/config
context: 'awx/192-168-64-4:8443/developer'
# Use a custom config file, and a specific context.
plugin: kubernetes.core.k8s
connections:
- kubeconfig: /path/to/config
context: 'awx/192-168-64-4:8443/developer'
@@ -357,15 +348,11 @@ Status
------
- This inventory will be removed in version 6.0.0. *[deprecated]*
- For more information see `DEPRECATED`_.
Authors
~~~~~~~
- Chris Houseknecht (@chouseknecht)
- Fabian von Feilitzsch (@fabianvf)
- Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch <@fabianvf>
.. hint::

View File

@@ -18,7 +18,7 @@ Version added: 2.0.0
Synopsis
--------
- 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.
@@ -27,8 +27,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
- jsonpatch
@@ -155,41 +155,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -213,15 +178,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -257,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>
</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>
@@ -614,7 +558,7 @@ Examples
path: /metadata/labels/app
value: myapp
- op: replace
path: /spec/containers/0/image
patch: /spec/containers/0/image
value: nginx

View File

@@ -28,8 +28,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -44,27 +44,6 @@ Parameters
<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>all_containers</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.4.0</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>If set to <code>true</code>, retrieve all containers&#x27; logs in the pod(s).</div>
<div>mutually exclusive with <code>container</code>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -162,8 +141,7 @@ Parameters
<td>
<div>Use to specify the container within a pod to grab the log from.</div>
<div>If there is only one container, this will default to that container.</div>
<div>If there is more than one container, this option is required or set <em>all_containers</em> to <code>true</code>.</div>
<div>mutually exclusive with <code>all_containers</code>.</div>
<div>If there is more than one container, this option is required.</div>
</td>
</tr>
<tr>
@@ -196,41 +174,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -255,15 +198,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -277,7 +218,6 @@ Parameters
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of label selectors to use to filter results</div>
@@ -319,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>
</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>
@@ -377,26 +298,6 @@ Parameters
<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>previous</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.4.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>If <code>true</code>, print the logs for the previous instance of the container in a pod if it exists.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -482,38 +383,6 @@ Parameters
</td>
</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>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>tail_lines</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.4.0</div>
</td>
<td>
</td>
<td>
<div>A number of lines from the end of the logs to retrieve.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -588,7 +457,6 @@ Examples
kind: Deployment
namespace: testing
name: example
since_seconds: "4000"
register: log
# This will get the log from a single Pod managed by this DeploymentConfig
@@ -598,16 +466,8 @@ Examples
kind: DeploymentConfig
namespace: testing
name: example
tail_lines: 100
register: log
# This will get the logs from all containers in Pod
- name: Get the logs from all containers in pod
kubernetes.core.k8s_log:
namespace: testing
name: some-pod
all_containers: true
Return Values

View File

@@ -26,8 +26,8 @@ Requirements
------------
The below requirements are needed on the local Ansible controller node that executes this lookup.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -381,12 +381,6 @@ Parameters
<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
@@ -396,23 +390,23 @@ Examples
- name: Fetch a list of namespaces
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
set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
- name: Fetch all deployments in a namespace
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
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
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
@@ -422,11 +416,11 @@ Examples
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
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
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">
<tr>
<th colspan="1">Key</th>
<th colspan="2">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<td colspan="2">
<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">list</span>
/ <span style="color: purple">elements=dictionary</span>
<span style="color: purple">complex</span>
</div>
</td>
<td></td>
<td>
<div>One ore more object definitions returned from the API.</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;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>
</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/>
@@ -471,8 +543,8 @@ Status
Authors
~~~~~~~
- Chris Houseknecht (@chouseknecht)
- Fabian von Feilitzsch (@fabianvf)
- Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch <@fabianvf>
.. hint::

View File

@@ -29,8 +29,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
- jsonpatch
@@ -121,7 +121,7 @@ Parameters
<td>
<div><code>apply</code> compares the desired resource definition with the previously supplied resource definition, ignoring properties that are automatically generated</div>
<div><code>apply</code> works better with Services than &#x27;force=yes&#x27;</div>
<div>Mutually exclusive with <code>merge_type</code>.</div>
<div>mutually exclusive with <code>merge_type</code></div>
</td>
</tr>
<tr>
@@ -208,30 +208,6 @@ Parameters
<div>This has no effect on the validation step which is controlled by the <code>validate.fail_on_error</code> parameter.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>delete_all</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.5.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>When this option is set to <em>true</em> and <em>state=absent</em>, module will delete all resources of the specified resource type in the requested namespace.</div>
<div>Ignored when <code>state</code> is not set to <em>absent</em> or when one of (src), <code>name</code> or <code>resource_definition</code> is provided.</div>
<div>Parameter <code>kind</code> is required to use this option.</div>
<div>This parameter can be used with <code>label_selectors</code> to restrict the resources to be deleted.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: all</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -360,45 +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>
</td>
</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>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>hidden_fields</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.5.0</div>
</td>
<td>
</td>
<td>
<div>Hide fields matching this option in the result</div>
<div>An example might be <code>hidden_fields=[metadata.managedFields]</code></div>
<div>Only field definitions that don&#x27;t reference list items are supported (so V(spec.containers[0]) would not work)</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -414,41 +351,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -473,32 +375,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
</tr>
<tr>
@@ -513,17 +396,18 @@ Parameters
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>json</li>
<li>merge</li>
<li>strategic-merge</li>
</ul>
</td>
<td>
<div>Whether to override the default patch merge approach with a specific type. By default, the strategic merge will typically be used.</div>
<div>For example, Custom Resource Definitions typically aren&#x27;t updatable by the usual strategic merge. You may want to use <code>merge</code> if you see &quot;strategic merge patch format is not supported&quot;.</div>
<div>For example, Custom Resource Definitions typically aren&#x27;t updatable by the usual strategic merge. You may want to use <code>merge</code> if you see &quot;strategic merge patch format is not supported&quot;</div>
<div>See <a href='https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment'>https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment</a></div>
<div>If more than one <code>merge_type</code> is given, the merge_types will be tried in order. This defaults to <code>[&#x27;strategic-merge&#x27;, &#x27;merge&#x27;]</code>, which is ideal for using the same parameters on resource kinds that combine Custom Resources and built-in resources.</div>
<div>Mutually exclusive with <code>apply</code>.</div>
<div><em>merge_type=json</em> has been removed in version 4.0.0. Please use <span class='module'>kubernetes.core.k8s_json_patch</span> instead.</div>
<div>mutually exclusive with <code>apply</code></div>
<div><em>merge_type=json</em> is deprecated and will be removed in version 3.0.0. Please use <span class='module'>kubernetes.core.k8s_json_patch</span> instead.</div>
</td>
</tr>
<tr>
@@ -562,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>
</td>
</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>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -722,63 +587,6 @@ Parameters
<div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
</td>
</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>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -793,8 +601,7 @@ Parameters
<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>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>The URL to manifest files that can be used to create the resource. Added in version 2.4.0.</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>
</tr>
<tr>
@@ -832,7 +639,6 @@ Parameters
<td>
<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>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>Template files needs to be present on the Ansible Controller&#x27;s file system.</div>
<div>Additional parameters can be specified using dictionary.</div>
@@ -1156,14 +962,6 @@ Examples
state: present
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
- name: >-
(Alternative) Read definition file from the Ansible controller file system.
In this case, the definition file contains multiple YAML documents, separated by ---.
If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
kubernetes.core.k8s:
state: present
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml_all }}"
- name: Read definition template file from the Ansible controller file system
kubernetes.core.k8s:
state: present
@@ -1177,15 +975,6 @@ Examples
variable_start_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
kubernetes.core.k8s:
state: present
@@ -1241,41 +1030,6 @@ Examples
labels:
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
# Delete all Deployment from specified namespace
- name: Delete all Deployment from specified namespace
kubernetes.core.k8s:
api_version: apps/v1
namespace: testing
kind: Deployment
delete_all: true
Return Values

View File

@@ -27,8 +27,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -152,7 +152,6 @@ Parameters
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of field selectors to use to filter results.</div>
@@ -173,41 +172,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -232,15 +196,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -254,7 +216,6 @@ Parameters
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of label selectors to use to filter results.</div>
@@ -296,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>
</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>

View File

@@ -25,8 +25,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
- PyYAML >= 3.11
@@ -189,41 +189,6 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
@@ -248,15 +213,13 @@ Parameters
<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>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -271,7 +234,6 @@ Parameters
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">[]</div>
</td>
<td>
<div>List of label selectors to use to filter results.</div>
@@ -313,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>
</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>
@@ -518,8 +461,7 @@ Parameters
<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>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>The URL to manifest files that can be used to create the resource. Added in version 2.4.0.</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>
</tr>
<tr>

View File

@@ -24,8 +24,8 @@ Requirements
------------
The below requirements are needed on the host that executes this module.
- python >= 3.9
- kubernetes >= 24.2.0
- python >= 3.6
- kubernetes >= 12.0.0
Parameters
@@ -172,56 +172,19 @@ Parameters
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
<span style="color: purple">path</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>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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>
@@ -281,25 +244,6 @@ Parameters
<div>Use to specify a Service object namespace.</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>
@@ -488,8 +432,7 @@ Parameters
<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>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>The URL to manifest files that can be used to create the resource. Added in version 2.4.0.</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>
</tr>
<tr>

View File

@@ -1,661 +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.9
- kubernetes >= 24.2.0
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="2">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>api_key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>ca_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>client_cert</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>client_key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>context</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>host</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_groups</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Group(s) to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>impersonate_user</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
</td>
<td>
</td>
<td>
<div>Username to impersonate for the operation.</div>
<div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
<div>Multiple Kubernetes config file can be provided using separator &#x27;;&#x27; for Windows platform or &#x27;:&#x27; for others platforms.</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

@@ -208,28 +208,6 @@ Parameters
</td>
<td>
<div>Path to a kubectl config file. Defaults to <em>~/.kube/config</em></div>
<div>The configuration can be provided as dictionary. Added in version 2.4.0.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubectl_local_env_vars</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 3.1.0</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">{}</div>
</td>
<td>
<div>var: ansible_kubectl_local_env_vars</div>
</td>
<td>
<div>Local enviromantal variable to be passed locally to the kubectl command line.</div>
<div>Please be aware that this passes information directly on the command line and it could expose sensitive data.</div>
</td>
</tr>
<tr>
@@ -365,82 +343,6 @@ Parameters
Examples
--------
.. code-block:: yaml
- name: Run a command in a pod using local kubectl with kubeconfig file ~/.kube/config
hosts: localhost
gather_facts: no
vars:
ansible_connection: kubernetes.core.kubectl
ansible_kubectl_namespace: my-namespace
ansible_kubectl_pod: my-pod
ansible_kubectl_container: my-container
tasks:
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
- name: Run a command in a pod
ansible.builtin.command: echo "Hello, World!"
- name: Run a command in a pod using local kubectl with inventory variables
# Example inventory:
# k8s:
# hosts:
# foo.example.com:
# ansible_connection: kubernetes.core.kubectl
# ansible_kubectl_kubeconfig: /root/.kube/foo.example.com.config
# ansible_kubectl_pod: my-foo-pod
# ansible_kubectl_container: my-foo-container
# ansible_kubectl_namespace: my-foo-namespace
# bar.example.com:
# ansible_connection: kubernetes.core.kubectl
# ansible_kubectl_kubeconfig: /root/.kube/bar.example.com.config
# ansible_kubectl_pod: my-bar-pod
# ansible_kubectl_container: my-bar-container
# ansible_kubectl_namespace: my-bar-namespace
hosts: k8s
gather_facts: no
tasks:
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
- name: Run a command in a pod
ansible.builtin.command: echo "Hello, World!"
- name: Run a command in a pod using dynamic inventory
hosts: localhost
gather_facts: no
vars:
kubeconfig: /root/.kube/config
namespace: my-namespace
my_app: my-app
tasks:
- name: Get My App pod info based on label
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig }}"
namespace: "{{ namespace }}"
kind: Pod
label_selectors: app.kubernetes.io/name = "{{ my_app }}"
register: my_app_pod
- name: Get My App pod name
ansible.builtin.set_fact:
my_app_pod_name: "{{ my_app_pod.resources[0].metadata.name }}"
- name: Add My App pod to inventory
ansible.builtin.add_host:
name: "{{ my_app_pod_name }}"
ansible_connection: kubernetes.core.kubectl
ansible_kubectl_kubeconfig: "{{ kubeconfig }}"
ansible_kubectl_pod: "{{ my_app_pod_name }}"
ansible_kubectl_namespace: "{{ namespace }}"
- name: Run a command in My App pod
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
ansible.builtin.command: echo "Hello, World!"
delegate_to: "{{ my_app_pod_name }}"
@@ -452,7 +354,7 @@ Status
Authors
~~~~~~~
- xuxinkun (@xuxinkun)
- xuxinkun
.. hint::

View File

@@ -1,194 +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>enable_helm</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">"False"</div>
</td>
<td>
</td>
<td>
<div>Enable the helm chart inflation generator</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
ansible.builtin.set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
- name: Run lookup using kubectl kustomize
ansible.builtin.set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
- name: Create kubernetes resources for lookup output
kubernetes.core.k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
- name: Create kubernetes resources for lookup output with `--enable-helm` set
kubernetes.core.k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization', enable_helm=True) }}"
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

@@ -9,8 +9,8 @@ authors:
- mmazur (https://github.com/mmazur)
- jamescassell (https://github.com/jamescassell)
description: Kubernetes Collection for Ansible.
documentation: ""
homepage: ""
documentation: ''
homepage: ''
issues: https://github.com/ansible-collections/kubernetes.core/issues
license_file: LICENSE
namespace: kubernetes
@@ -25,7 +25,7 @@ tags:
- openshift
- okd
- cluster
version: 5.0.0
version: 2.1.1
build_ignore:
- .DS_Store
- "*.tar.gz"
- '*.tar.gz'

View File

@@ -1,5 +1,5 @@
---
requires_ansible: '>=2.15.0'
requires_ansible: '>=2.9.17'
action_groups:
helm:
@@ -9,23 +9,43 @@ action_groups:
k8s:
- k8s
- k8s_exec
- k8s_facts
- k8s_info
- k8s_log
- k8s_scale
- k8s_service
- k8s_cp
- k8s_drain
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:
openshift:
redirect: community.okd.openshift
k8s:
deprecation:
removal_version: 6.0.0
warning_text: >-
The k8s inventory plugin has been deprecated and
will be removed in release 6.0.0.
modules:
k8s_auth:
redirect: community.okd.k8s_auth

View File

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

View File

@@ -3,77 +3,43 @@
# Copyright (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import copy
import os
import platform
import traceback
from contextlib import contextmanager
from ansible.config.manager import ensure_type
from ansible.errors import (
AnsibleAction,
AnsibleActionFail,
AnsibleError,
AnsibleFileNotFound,
)
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import iteritems, string_types
from ansible.module_utils.six import string_types, iteritems
from ansible.module_utils._text import to_text, to_bytes, to_native
from ansible.plugins.action import ActionBase
class RemoveOmit(object):
def __init__(self, buffer, omit_value):
try:
import yaml
except ImportError:
raise AnsibleError("Failed to import the required Python library (PyYAML).")
self.data = yaml.safe_load_all(buffer)
self.omit = omit_value
def remove_omit(self, data):
if isinstance(data, dict):
result = dict()
for key, value in iteritems(data):
if value == self.omit:
continue
result[key] = self.remove_omit(value)
return result
if isinstance(data, list):
return [self.remove_omit(v) for v in data if v != self.omit]
return data
def output(self):
return [self.remove_omit(d) for d in self.data]
ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
class ActionModule(ActionBase):
TRANSFERS_FILES = True
DEFAULT_NEWLINE_SEQUENCE = "\n"
def _ensure_invocation(self, result):
# NOTE: adding invocation arguments here needs to be kept in sync with
# 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:
result["invocation"] = "CENSORED: no_log is set"
result['invocation'] = "CENSORED: no_log is set"
else:
result["invocation"] = self._task.args.copy()
result["invocation"]["module_args"] = self._task.args.copy()
result['invocation'] = self._task.args.copy()
result['invocation']['module_args'] = self._task.args.copy()
return result
@contextmanager
def get_template_data(self, template_path):
try:
source = self._find_needle("templates", template_path)
source = self._find_needle('templates', template_path)
except AnsibleError as e:
raise AnsibleActionFail(to_text(e))
@@ -81,19 +47,15 @@ class ActionModule(ActionBase):
try:
tmp_source = self._loader.get_real_file(source)
except AnsibleFileNotFound as e:
raise AnsibleActionFail(
"could not find template=%s, %s" % (source, to_text(e))
)
b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
raise AnsibleActionFail("could not find template=%s, %s" % (source, to_text(e)))
b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict')
try:
with open(b_tmp_source, "rb") as f:
with open(b_tmp_source, 'rb') as f:
try:
template_data = to_text(f.read(), errors="surrogate_or_strict")
template_data = to_text(f.read(), errors='surrogate_or_strict')
except UnicodeError:
raise AnsibleActionFail(
"Template source files must be utf-8 encoded"
)
raise AnsibleActionFail("Template source files must be utf-8 encoded")
yield template_data
except AnsibleAction:
raise
@@ -110,104 +72,63 @@ class ActionModule(ActionBase):
"block_start_string": None,
"block_end_string": None,
"trim_blocks": True,
"lstrip_blocks": False,
"lstrip_blocks": False
}
if isinstance(template, string_types):
# treat this as raw_params
template_param["path"] = template
template_param['path'] = template
elif isinstance(template, dict):
template_args = template
template_path = template_args.get("path", None)
template_path = template_args.get('path', None)
if not template_path:
raise AnsibleActionFail("Please specify path for template.")
template_param["path"] = template_path
template_param['path'] = template_path
# Options type validation strings
for s_type in (
"newline_sequence",
"variable_start_string",
"variable_end_string",
"block_start_string",
"block_end_string",
):
for s_type in ('newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string',
'block_end_string'):
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):
raise AnsibleActionFail(
"%s is expected to be a string, but got %s instead"
% (s_type, type(value))
)
raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
try:
template_param.update(
{
"trim_blocks": boolean(
template_args.get("trim_blocks", True), strict=False
),
"lstrip_blocks": boolean(
template_args.get("lstrip_blocks", False), strict=False
),
}
)
template_param.update({
"trim_blocks": boolean(template_args.get('trim_blocks', True), strict=False),
"lstrip_blocks": boolean(template_args.get('lstrip_blocks', False), strict=False)
})
except TypeError as e:
raise AnsibleActionFail(to_native(e))
template_param.update(
{
"newline_sequence": template_args.get(
"newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
),
"variable_start_string": template_args.get(
"variable_start_string", None
),
"variable_end_string": template_args.get(
"variable_end_string", None
),
"block_start_string": template_args.get("block_start_string", None),
"block_end_string": template_args.get("block_end_string", None),
}
)
template_param.update({
"newline_sequence": template_args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE),
"variable_start_string": template_args.get('variable_start_string', None),
"variable_end_string": template_args.get('variable_end_string', None),
"block_start_string": template_args.get('block_start_string', None),
"block_end_string": template_args.get('block_end_string', None)
})
else:
raise AnsibleActionFail(
"Error while reading template file - "
"a string or dict for template expected, but got %s instead"
% type(template)
)
raise AnsibleActionFail("Error while reading template file - "
"a string or dict for template expected, but got %s instead" % type(template))
return template_param
def import_jinja2_lstrip(self, templates):
# 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:
import jinja2.defaults
except ImportError:
raise AnsibleError(
"Unable to import Jinja2 defaults for determining Jinja2 features."
)
raise AnsibleError('Unable to import Jinja2 defaults for determining Jinja2 features.')
try:
jinja2.defaults.LSTRIP_BLOCKS
except AttributeError:
raise AnsibleError(
"Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
)
raise AnsibleError("Option `lstrip_blocks' is only available in Jinja2 versions >=2.7")
def load_template(self, template, new_module_args, task_vars):
# template is only supported by k8s module.
if self._task.action not in (
"k8s",
"kubernetes.core.k8s",
"community.okd.k8s",
"redhat.openshift.k8s",
"community.kubernetes.k8s",
"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."
)
if self._task.action not in ('k8s', 'kubernetes.core.k8s', 'community.okd.k8s'):
raise AnsibleActionFail("'template' is only supported parameter for 'k8s' module.")
omit_value = task_vars.get("omit")
template_params = []
if isinstance(template, string_types) or isinstance(template, dict):
template_params.append(self.get_template_args(template))
@@ -215,11 +136,8 @@ class ActionModule(ActionBase):
for element in template:
template_params.append(self.get_template_args(element))
else:
raise AnsibleActionFail(
"Error while reading template file - "
"a string or dict for template expected, but got %s instead"
% type(template)
)
raise AnsibleActionFail("Error while reading template file - "
"a string or dict for template expected, but got %s instead" % type(template))
self.import_jinja2_lstrip(template_params)
@@ -230,31 +148,20 @@ class ActionModule(ActionBase):
old_vars = self._templar.available_variables
default_environment = {}
for key in (
"newline_sequence",
"variable_start_string",
"variable_end_string",
"block_start_string",
"block_end_string",
"trim_blocks",
"lstrip_blocks",
):
for key in ("newline_sequence", "variable_start_string", "variable_end_string",
"block_start_string", "block_end_string", "trim_blocks", "lstrip_blocks"):
if hasattr(self._templar.environment, key):
default_environment[key] = getattr(self._templar.environment, key)
for template_item in template_params:
# 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:
template_item["newline_sequence"] = allowed_sequences[
wrong_sequences.index(newline_sequence)
]
template_item['newline_sequence'] = allowed_sequences[wrong_sequences.index(newline_sequence)]
elif newline_sequence not in allowed_sequences:
raise AnsibleActionFail(
"newline_sequence needs to be one of: \n, \r or \r\n"
)
raise AnsibleActionFail("newline_sequence needs to be one of: \n, \r or \r\n")
# template the source data locally & get ready to transfer
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
temp_vars = copy.deepcopy(task_vars)
for key, value in iteritems(template_item):
@@ -262,75 +169,18 @@ class ActionModule(ActionBase):
if value is not None:
setattr(self._templar.environment, key, value)
else:
setattr(
self._templar.environment,
key,
default_environment.get(key),
)
setattr(self._templar.environment, key, default_environment.get(key))
self._templar.available_variables = temp_vars
result = self._templar.do_template(
template_data,
preserve_trailing_newlines=True,
escape_backslashes=False,
)
if omit_value is not None:
result_template.extend(RemoveOmit(result, omit_value).output())
else:
result_template.append(result)
result = self._templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
result_template.append(result)
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:
new_module_args.pop("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
configs = []
for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
config = self._find_needle("files", config)
# decrypt kubeconfig found
configs.append(self._loader.get_real_file(config, decrypt=True))
new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
configs
)
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)
)
new_module_args.pop('template')
new_module_args['definition'] = result_template
def run(self, tmp=None, task_vars=None):
"""handler for k8s options"""
''' handler for k8s options '''
if task_vars is None:
task_vars = dict()
@@ -341,61 +191,55 @@ class ActionModule(ActionBase):
# look for kubeconfig and src
# 'local' => look files on Ansible Controller
# 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)
kubeconfig = self._task.args.get("kubeconfig", None)
if kubeconfig:
kubeconfig = self._task.args.get('kubeconfig', None)
# find the kubeconfig in the expected search path
if kubeconfig and not remote_transport:
# kubeconfig is local
try:
self.get_kubeconfig(kubeconfig, remote_transport, new_module_args)
# find in expected paths
kubeconfig = self._find_needle('files', kubeconfig)
except AnsibleError as e:
result["failed"] = True
result["msg"] = to_text(e)
result["exception"] = traceback.format_exc()
result['failed'] = True
result['msg'] = to_text(e)
result['exception'] = traceback.format_exc()
return result
# find the file in the expected search path
src = self._task.args.get("src", None)
# decrypt kubeconfig found
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
new_module_args['kubeconfig'] = actual_file
if src and not src.startswith(("http://", "https://", "ftp://")):
# find the file in the expected search path
src = self._task.args.get('src', None)
if src:
if remote_transport:
# src is on remote node
result.update(
self._execute_module(
module_name=self._task.action, task_vars=task_vars
)
)
result.update(self._execute_module(module_name=self._task.action, task_vars=task_vars))
return self._ensure_invocation(result)
# src is local
try:
# find in expected paths
src = self._find_needle("files", src)
src = self._find_needle('files', src)
except AnsibleError as e:
result["failed"] = True
result["msg"] = to_text(e)
result["exception"] = traceback.format_exc()
result['failed'] = True
result['msg'] = to_text(e)
result['exception'] = traceback.format_exc()
return result
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:
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" and not remote_transport:
new_module_args["local_path"] = self.get_file_realpath(local_path)
# Execute the k8s_* module.
module_return = self._execute_module(
module_name=self._task.action,
module_args=new_module_args,
task_vars=task_vars,
)
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
# Delete tmp path
self._remove_tmp_path(self._connection._shell.tmpdir)

View File

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

View File

@@ -17,15 +17,14 @@
#
# You should have received a copy of the GNU General Public License
# 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
DOCUMENTATION = r"""
author:
- xuxinkun (@xuxinkun)
- xuxinkun
name: kubectl
connection: kubectl
short_description: Execute tasks in pods running on Kubernetes.
@@ -72,19 +71,9 @@ DOCUMENTATION = r"""
- name: ansible_kubectl_extra_args
env:
- name: K8S_AUTH_EXTRA_ARGS
kubectl_local_env_vars:
description:
- Local enviromantal variable to be passed locally to the kubectl command line.
- Please be aware that this passes information directly on the command line and it could expose sensitive data.
default: {}
type: dict
version_added: 3.1.0
vars:
- name: ansible_kubectl_local_env_vars
kubectl_kubeconfig:
description:
- Path to a kubectl config file. Defaults to I(~/.kube/config)
- The configuration can be provided as dictionary. Added in version 2.4.0.
default: ''
vars:
- name: ansible_kubectl_kubeconfig
@@ -181,118 +170,41 @@ DOCUMENTATION = r"""
aliases: [ kubectl_verify_ssl ]
"""
EXAMPLES = r"""
- name: Run a command in a pod using local kubectl with kubeconfig file ~/.kube/config
hosts: localhost
gather_facts: no
vars:
ansible_connection: kubernetes.core.kubectl
ansible_kubectl_namespace: my-namespace
ansible_kubectl_pod: my-pod
ansible_kubectl_container: my-container
tasks:
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
- name: Run a command in a pod
ansible.builtin.command: echo "Hello, World!"
- name: Run a command in a pod using local kubectl with inventory variables
# Example inventory:
# k8s:
# hosts:
# foo.example.com:
# ansible_connection: kubernetes.core.kubectl
# ansible_kubectl_kubeconfig: /root/.kube/foo.example.com.config
# ansible_kubectl_pod: my-foo-pod
# ansible_kubectl_container: my-foo-container
# ansible_kubectl_namespace: my-foo-namespace
# bar.example.com:
# ansible_connection: kubernetes.core.kubectl
# ansible_kubectl_kubeconfig: /root/.kube/bar.example.com.config
# ansible_kubectl_pod: my-bar-pod
# ansible_kubectl_container: my-bar-container
# ansible_kubectl_namespace: my-bar-namespace
hosts: k8s
gather_facts: no
tasks:
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
- name: Run a command in a pod
ansible.builtin.command: echo "Hello, World!"
- name: Run a command in a pod using dynamic inventory
hosts: localhost
gather_facts: no
vars:
kubeconfig: /root/.kube/config
namespace: my-namespace
my_app: my-app
tasks:
- name: Get My App pod info based on label
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig }}"
namespace: "{{ namespace }}"
kind: Pod
label_selectors: app.kubernetes.io/name = "{{ my_app }}"
register: my_app_pod
- name: Get My App pod name
ansible.builtin.set_fact:
my_app_pod_name: "{{ my_app_pod.resources[0].metadata.name }}"
- name: Add My App pod to inventory
ansible.builtin.add_host:
name: "{{ my_app_pod_name }}"
ansible_connection: kubernetes.core.kubectl
ansible_kubectl_kubeconfig: "{{ kubeconfig }}"
ansible_kubectl_pod: "{{ my_app_pod_name }}"
ansible_kubectl_namespace: "{{ namespace }}"
- name: Run a command in My App pod
# be aware that the command is executed as the user that started the container
# and requires python to be installed in the image
ansible.builtin.command: echo "Hello, World!"
delegate_to: "{{ my_app_pod_name }}"
"""
import json
import distutils.spawn
import os
import os.path
import shutil
import subprocess
import tempfile
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils._text import to_bytes
from ansible.module_utils.six.moves import shlex_quote
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.plugins.connection import BUFSIZE, ConnectionBase
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils._text import to_bytes
from ansible.plugins.connection import ConnectionBase, BUFSIZE
from ansible.utils.display import Display
display = Display()
CONNECTION_TRANSPORT = "kubectl"
CONNECTION_TRANSPORT = 'kubectl'
CONNECTION_OPTIONS = {
"kubectl_container": "-c",
"kubectl_namespace": "-n",
"kubectl_kubeconfig": "--kubeconfig",
"kubectl_context": "--context",
"kubectl_host": "--server",
"kubectl_username": "--username",
"kubectl_password": "--password",
"client_cert": "--client-certificate",
"client_key": "--client-key",
"ca_cert": "--certificate-authority",
"validate_certs": "--insecure-skip-tls-verify",
"kubectl_token": "--token",
'kubectl_container': '-c',
'kubectl_namespace': '-n',
'kubectl_kubeconfig': '--kubeconfig',
'kubectl_context': '--context',
'kubectl_host': '--server',
'kubectl_username': '--username',
'kubectl_password': '--password',
'client_cert': '--client-certificate',
'client_key': '--client-key',
'ca_cert': '--certificate-authority',
'validate_certs': '--insecure-skip-tls-verify',
'kubectl_token': '--token'
}
class Connection(ConnectionBase):
"""Local kubectl based connections"""
''' Local kubectl based connections '''
transport = CONNECTION_TRANSPORT
connection_options = CONNECTION_OPTIONS
@@ -305,246 +217,153 @@ class Connection(ConnectionBase):
# Note: kubectl runs commands as the user that started the container.
# It is impossible to set the remote user for a kubectl connection.
cmd_arg = "{0}_command".format(self.transport)
self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
if not self.transport_cmd:
raise AnsibleError("{0} command not found in PATH".format(self.transport))
self._file_to_delete = None
def delete_temporary_file(self):
if self._file_to_delete is not None:
os.remove(self._file_to_delete)
self._file_to_delete = None
cmd_arg = '{0}_command'.format(self.transport)
if cmd_arg in kwargs:
self.transport_cmd = kwargs[cmd_arg]
else:
self.transport_cmd = distutils.spawn.find_executable(self.transport)
if not self.transport_cmd:
raise AnsibleError("{0} command not found in PATH".format(self.transport))
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]
censored_local_cmd = [self.transport_cmd]
# Build command options based on doc string
doc_yaml = AnsibleLoader(self.documentation).get_single_data()
for key in doc_yaml.get("options"):
if key.endswith("verify_ssl") and self.get_option(key) != "":
for key in doc_yaml.get('options'):
if key.endswith('verify_ssl') and self.get_option(key) != '':
# Translate verify_ssl to skip_verify_ssl, and output as string
skip_verify_ssl = not self.get_option(key)
local_cmd.append(
"{0}={1}".format(
self.connection_options[key], str(skip_verify_ssl).lower()
)
)
censored_local_cmd.append(
"{0}={1}".format(
self.connection_options[key], str(skip_verify_ssl).lower()
)
)
elif key.endswith("kubeconfig") and self.get_option(key) != "":
kubeconfig_path = self.get_option(key)
if isinstance(kubeconfig_path, dict):
fd, tmpfile = tempfile.mkstemp()
with os.fdopen(fd, "w") as fp:
json.dump(kubeconfig_path, fp)
kubeconfig_path = tmpfile
self._file_to_delete = tmpfile
cmd_arg = self.connection_options[key]
local_cmd += [cmd_arg, kubeconfig_path]
censored_local_cmd += [cmd_arg, kubeconfig_path]
elif (
not key.endswith("container")
and self.get_option(key)
and self.connection_options.get(key)
):
local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
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]
local_cmd += [cmd_arg, self.get_option(key)]
# Redact password and token from console log
if key.endswith(("_token", "_password")):
censored_local_cmd += [cmd_arg, "********"]
else:
censored_local_cmd += [cmd_arg, self.get_option(key)]
if key.endswith(('_token', '_password')):
censored_local_cmd += [cmd_arg, '********']
extra_args_name = "{0}_extra_args".format(self.transport)
extra_args_name = u'{0}_extra_args'.format(self.transport)
if self.get_option(extra_args_name):
local_cmd += self.get_option(extra_args_name).split(" ")
censored_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(' ')
pod = self.get_option("{0}_pod".format(self.transport))
pod = self.get_option(u'{0}_pod'.format(self.transport))
if not pod:
pod = self._play_context.remote_addr
# -i is needed to keep stdin open which allows pipelining to work
local_cmd += ["exec", "-i", pod]
censored_local_cmd += ["exec", "-i", pod]
local_cmd += ['exec', '-i', pod]
censored_local_cmd += ['exec', '-i', pod]
# if the pod has more than one container, then container is required
container_arg_name = "{0}_container".format(self.transport)
container_arg_name = u'{0}_container'.format(self.transport)
if self.get_option(container_arg_name):
local_cmd += ["-c", self.get_option(container_arg_name)]
censored_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)]
local_cmd += ["--"] + cmd
censored_local_cmd += ["--"] + cmd
local_cmd += ['--'] + cmd
censored_local_cmd += ['--'] + cmd
return local_cmd, censored_local_cmd
def _local_env(self):
"""Return a dict of local environment variables to pass to the kubectl command"""
local_env = {}
local_local_env_vars_name = "{0}_local_env_vars".format(self.transport)
local_env_vars = self.get_option(local_local_env_vars_name)
if local_env_vars:
if isinstance(local_env_vars, dict):
local_env_vars = json.dumps(local_env_vars)
local_env = os.environ.copy()
local_env.update(json.loads(local_env_vars))
return local_env
return None
def _connect(self, port=None):
"""Connect to the container. Nothing to do"""
""" Connect to the container. Nothing to do """
super(Connection, self)._connect()
if not self._connected:
display.vvv(
"ESTABLISH {0} CONNECTION".format(self.transport),
host=self._play_context.remote_addr,
)
display.vvv(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._play_context.remote_addr)
self._connected = True
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)
local_cmd, censored_local_cmd = self._build_exec_cmd(
[self._play_context.executable, "-c", cmd]
)
local_cmd, censored_local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
display.vvv(
"EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr
)
local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
p = subprocess.Popen(
local_cmd,
shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._local_env(),
)
display.vvv("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,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(in_data)
self.delete_temporary_file()
return (p.returncode, stdout, stderr)
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.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
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
exist in any given chroot. So for now we're choosing "/" instead.
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):
remote_path = os.path.join(os.path.sep, remote_path)
return os.path.normpath(remote_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)
display.vvv(
"PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
)
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
out_path = self._prefix_login_path(out_path)
if not os.path.exists(to_bytes(in_path, errors="surrogate_or_strict")):
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
raise AnsibleFileNotFound(
"file or module does not exist: %s" % in_path)
out_path = shlex_quote(out_path)
# kubectl doesn't have native support for copying files into
# 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:
count = " count=0"
count = ' count=0'
else:
count = ""
args, dummy = self._build_exec_cmd(
[
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]
count = ''
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]
try:
p = subprocess.Popen(
args,
stdin=in_file,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._local_env(),
)
p = subprocess.Popen(args, stdin=in_file,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError:
raise AnsibleError(
"kubectl connection requires dd command in the container to put files"
)
raise AnsibleError("kubectl connection requires dd command in the container to put files")
stdout, stderr = p.communicate()
self.delete_temporary_file()
if p.returncode != 0:
raise AnsibleError(
"failed to transfer file %s to %s:\n%s\n%s"
% (in_path, out_path, stdout, stderr)
)
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
def fetch_file(self, in_path, out_path):
"""Fetch a file from container to local."""
""" Fetch a file from container to local. """
super(Connection, self).fetch_file(in_path, out_path)
display.vvv(
"FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
)
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
in_path = self._prefix_login_path(in_path)
out_dir = os.path.dirname(out_path)
# kubectl doesn't have native support for fetching files from
# running containers, so we use kubectl exec to implement this
args, dummy = self._build_exec_cmd(
[self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)]
)
args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
with open(
to_bytes(actual_out_path, errors="surrogate_or_strict"), "wb"
) as out_file:
with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
try:
p = subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=out_file,
stderr=subprocess.PIPE,
env=self._local_env(),
)
p = subprocess.Popen(args, stdin=subprocess.PIPE,
stdout=out_file, stderr=subprocess.PIPE)
except OSError:
raise AnsibleError(
"{0} connection requires dd command in the container to fetch files".format(
self.transport
)
"{0} connection requires dd command in the container to fetch files".format(self.transport)
)
stdout, stderr = p.communicate()
self.delete_temporary_file()
if p.returncode != 0:
raise AnsibleError(
"failed to fetch file %s to %s:\n%s\n%s"
% (in_path, out_path, stdout, stderr)
)
raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
if actual_out_path != out_path:
os.rename(
to_bytes(actual_out_path, errors="strict"),
to_bytes(out_path, errors="strict"),
)
os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
def close(self):
"""Terminate the connection. Nothing to do for kubectl"""
""" Terminate the connection. Nothing to do for kubectl"""
super(Connection, self).close()
self._connected = False

View File

@@ -6,13 +6,13 @@
# Options for common Helm modules
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r"""
DOCUMENTATION = r'''
options:
binary_path:
description:
@@ -29,19 +29,18 @@ options:
description:
- Helm option to specify kubeconfig path to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
- The configuration can be provided as dictionary. Added in version 2.4.0.
type: raw
type: path
aliases: [ kubeconfig_path ]
host:
description:
- Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
type: str
version_added: 1.2.0
version_added: "1.2.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: 1.2.0
version_added: "1.2.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)
@@ -49,12 +48,12 @@ options:
type: bool
aliases: [ verify_ssl ]
default: True
version_added: 1.2.0
version_added: "1.2.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: 1.2.0
"""
version_added: "1.2.0"
'''

View File

@@ -5,13 +5,13 @@
# Options for authenticating with the API.
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r"""
DOCUMENTATION = r'''
options:
host:
description:
@@ -27,9 +27,7 @@ options:
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
variable.
- Multiple Kubernetes config file can be provided using separator ';' for Windows platform or ':' for others platforms.
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.
type: raw
type: path
context:
description:
- 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.
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
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:
description:
- 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.
- "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169"
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:
- "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
kubeconfig file."
"""
'''

View File

@@ -5,17 +5,17 @@
# Options for specifying object wait
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r"""
DOCUMENTATION = r'''
options:
delete_options:
type: dict
version_added: 1.2.0
version_added: '1.2.0'
description:
- Configure behavior when deleting an object.
- Only used when I(state=absent).
@@ -48,4 +48,4 @@ options:
type: str
description:
- Specify the UID of the target object.
"""
'''

View File

@@ -5,13 +5,13 @@
# 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
class ModuleDocFragment(object):
DOCUMENTATION = r"""
DOCUMENTATION = r'''
options:
api_version:
description:
@@ -49,4 +49,4 @@ options:
- If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
will override this option.
type: str
"""
'''

View File

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

View File

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

View File

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

View File

@@ -5,13 +5,13 @@
# Options for specifying object wait
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r"""
DOCUMENTATION = r'''
options:
wait:
description:
@@ -64,4 +64,4 @@ options:
- 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.
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)
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleFilterError
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
generate_hash,
)
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import generate_hash
def k8s_config_resource_name(resource):
@@ -18,14 +15,15 @@ def k8s_config_resource_name(resource):
Generate resource name for the given resource of type ConfigMap, Secret
"""
try:
return resource["metadata"]["name"] + "-" + generate_hash(resource)
return resource['metadata']['name'] + '-' + generate_hash(resource)
except KeyError:
raise AnsibleFilterError(
"resource must have a metadata.name key to generate a resource name"
)
raise AnsibleFilterError("resource must have a metadata.name key to generate a resource name")
# ---- Ansible filters ----
class FilterModule(object):
def filters(self):
return {"k8s_config_resource_name": k8s_config_resource_name}
return {
'k8s_config_resource_name': k8s_config_resource_name
}

View File

@@ -1,36 +0,0 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
DOCUMENTATION:
name: k8s_config_resource_name
short_description: Generate resource name for the given resource of type ConfigMap, Secret
description:
- Generate resource name for the given resource of type ConfigMap, Secret.
- Resource must have a C(metadata.name) key to generate a resource name
options:
_input:
description:
- A valid YAML definition for a ConfigMap or a Secret.
type: dict
required: true
author:
- ansible cloud team
EXAMPLES: |
# Dump generated name for a configmap into a variable
- set_fact:
generated_name: '{{ definition | kubernetes.core.k8s_config_resource_name }}'
vars:
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: myconfigmap
namespace: mynamespace
RETURN:
_value:
description: Generated resource name.
type: str

View File

@@ -1,15 +1,15 @@
# Copyright (c) 2018 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
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
DOCUMENTATION = '''
name: k8s
plugin_type: inventory
author:
- Chris Houseknecht (@chouseknecht)
- Fabian von Feilitzsch (@fabianvf)
- Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch <@fabianvf>
short_description: Kubernetes (K8s) inventory source
@@ -19,13 +19,6 @@ DOCUMENTATION = """
- Uses the kubectl connection plugin to access the Kubernetes cluster.
- Uses k8s.(yml|yaml) YAML configuration file to set parameter values.
deprecated:
removed_in: 6.0.0
why: |
As discussed in U(https://github.com/ansible-collections/kubernetes.core/issues/31), we decided to
remove the k8s inventory plugin in release 6.0.0.
alternative: "Use M(kubernetes.core.k8s_info) and M(ansible.builtin.add_host) instead."
options:
plugin:
description: token that ensures this is a source file for the 'k8s' plugin.
@@ -93,93 +86,79 @@ DOCUMENTATION = """
to access.
requirements:
- "python >= 3.9"
- "kubernetes >= 24.2.0"
- "python >= 3.6"
- "kubernetes >= 12.0.0"
- "PyYAML >= 3.11"
"""
'''
EXAMPLES = r"""
EXAMPLES = '''
# File must be named k8s.yaml or k8s.yml
- name: Authenticate with token, and return all pods and services for all namespaces
plugin: kubernetes.core.k8s
connections:
- host: https://192.168.64.4:8443
api_key: xxxxxxxxxxxxxxxx
validate_certs: false
# Authenticate with token, and return all pods and services for all namespaces
plugin: kubernetes.core.k8s
connections:
- host: https://192.168.64.4:8443
api_key: xxxxxxxxxxxxxxxx
validate_certs: false
- name: Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
plugin: kubernetes.core.k8s
connections:
- namespaces:
- testing
# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
plugin: kubernetes.core.k8s
connections:
- namespaces:
- testing
- name: Use a custom config file, and a specific context.
plugin: kubernetes.core.k8s
connections:
- kubeconfig: /path/to/config
context: 'awx/192-168-64-4:8443/developer'
"""
# Use a custom config file, and a specific context.
plugin: kubernetes.core.k8s
connections:
- kubeconfig: /path/to/config
context: 'awx/192-168-64-4:8443/developer'
'''
import json
from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, Constructable
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER, k8s_import_exception, get_api_client
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
try:
from kubernetes.dynamic.exceptions import DynamicApiError
HAS_K8S_MODULE_HELPER = True
k8s_import_exception = None
except ImportError as e:
HAS_K8S_MODULE_HELPER = False
k8s_import_exception = e
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client,
)
except ImportError:
pass
def format_dynamic_api_exc(exc):
if exc.body:
if exc.headers and exc.headers.get("Content-Type") == "application/json":
message = json.loads(exc.body).get("message")
if exc.headers and exc.headers.get('Content-Type') == 'application/json':
message = json.loads(exc.body).get('message')
if message:
return message
return exc.body
else:
return "%s Reason: %s" % (exc.status, exc.reason)
return '%s Reason: %s' % (exc.status, exc.reason)
class K8sInventoryException(Exception):
pass
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = "kubernetes.core.k8s"
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin):
NAME = 'kubernetes.core.k8s'
connection_plugin = "kubernetes.core.kubectl"
transport = "kubectl"
connection_plugin = 'kubernetes.core.kubectl'
transport = 'kubectl'
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
self.display.deprecated(
"The 'k8s' inventory plugin has been deprecated and will be removed in release 6.0.0",
version="6.0.0",
collection_name="kubernetes.core",
)
cache_key = self._get_cache_prefix(path)
config_data = self._read_config_data(path)
self.setup(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:
raise K8sInventoryException(
"This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
k8s_import_exception
)
"This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
)
source_data = None
@@ -193,21 +172,18 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self.fetch_objects(connections)
def fetch_objects(self, connections):
if connections:
if not isinstance(connections, list):
raise K8sInventoryException("Expecting connections to be a list.")
for connection in connections:
if not isinstance(connection, dict):
raise K8sInventoryException(
"Expecting connection to be a dictionary."
)
raise K8sInventoryException("Expecting connection to be a dictionary.")
client = get_api_client(**connection)
name = connection.get(
"name", self.get_default_host_name(client.configuration.host)
)
if connection.get("namespaces"):
namespaces = connection["namespaces"]
name = connection.get('name', self.get_default_host_name(client.configuration.host))
if connection.get('namespaces'):
namespaces = connection['namespaces']
else:
namespaces = self.get_available_namespaces(client)
for namespace in namespaces:
@@ -223,36 +199,27 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
@staticmethod
def get_default_host_name(host):
return (
host.replace("https://", "")
.replace("http://", "")
.replace(".", "-")
.replace(":", "_")
)
return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_')
def get_available_namespaces(self, client):
v1_namespace = client.resources.get(api_version="v1", kind="Namespace")
v1_namespace = client.resources.get(api_version='v1', kind='Namespace')
try:
obj = v1_namespace.get()
except DynamicApiError as exc:
self.display.debug(exc)
raise K8sInventoryException(
"Error fetching Namespace list: %s" % format_dynamic_api_exc(exc)
)
raise K8sInventoryException('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc))
return [namespace.metadata.name for namespace in obj.items]
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:
obj = v1_pod.get(namespace=namespace)
except DynamicApiError as exc:
self.display.debug(exc)
raise K8sInventoryException(
"Error fetching Pod list: %s" % format_dynamic_api_exc(exc)
)
raise K8sInventoryException('Error fetching Pod list: %s' % format_dynamic_api_exc(exc))
namespace_group = "namespace_{0}".format(namespace)
namespace_pods_group = "{0}_pods".format(namespace_group)
namespace_group = 'namespace_{0}'.format(namespace)
namespace_pods_group = '{0}_pods'.format(namespace_group)
self.inventory.add_group(name)
self.inventory.add_group(namespace_group)
@@ -263,14 +230,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
for pod in obj.items:
pod_name = pod.metadata.name
pod_groups = []
pod_annotations = (
{} if not pod.metadata.annotations else dict(pod.metadata.annotations)
)
pod_annotations = {} if not pod.metadata.annotations else dict(pod.metadata.annotations)
if pod.metadata.labels:
# create a group for each label_value
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:
pod_groups.append(group_name)
self.inventory.add_group(group_name)
@@ -283,7 +248,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
for container in pod.status.containerStatuses:
# 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_child(namespace_pods_group, container_name)
if pod_groups:
@@ -291,85 +256,46 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self.inventory.add_child(group, container_name)
# Add hostvars
self.inventory.set_variable(container_name, "object_type", "pod")
self.inventory.set_variable(container_name, "labels", pod_labels)
self.inventory.set_variable(
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(container_name, "pod_name", pod.spec.name)
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(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
)
self.inventory.set_variable(container_name, 'object_type', 'pod')
self.inventory.set_variable(container_name, 'labels', pod_labels)
self.inventory.set_variable(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(container_name, 'pod_name', pod.spec.name)
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(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:
self.inventory.set_variable(
container_name, "container_state", "Running"
)
self.inventory.set_variable(container_name, 'container_state', 'Running')
if container.state.terminated:
self.inventory.set_variable(
container_name, "container_state", "Terminated"
)
self.inventory.set_variable(container_name, 'container_state', 'Terminated')
if container.state.waiting:
self.inventory.set_variable(
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(
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,
)
self.inventory.set_variable(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(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):
v1_service = client.resources.get(api_version="v1", kind="Service")
v1_service = client.resources.get(api_version='v1', kind='Service')
try:
obj = v1_service.get(namespace=namespace)
except DynamicApiError as exc:
self.display.debug(exc)
raise K8sInventoryException(
"Error fetching Service list: %s" % format_dynamic_api_exc(exc)
)
raise K8sInventoryException('Error fetching Service list: %s' % format_dynamic_api_exc(exc))
namespace_group = "namespace_{0}".format(namespace)
namespace_services_group = "{0}_services".format(namespace_group)
namespace_group = 'namespace_{0}'.format(namespace)
namespace_services_group = '{0}_services'.format(namespace_group)
self.inventory.add_group(name)
self.inventory.add_group(namespace_group)
@@ -379,21 +305,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
for service in obj.items:
service_name = service.metadata.name
service_labels = (
{} if not service.metadata.labels else dict(service.metadata.labels)
)
service_annotations = (
{}
if not service.metadata.annotations
else dict(service.metadata.annotations)
)
service_labels = {} if not service.metadata.labels else dict(service.metadata.labels)
service_annotations = {} if not service.metadata.annotations else dict(service.metadata.annotations)
self.inventory.add_host(service_name)
if service.metadata.labels:
# create a group for each label_value
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_child(group_name, service_name)
@@ -402,75 +322,42 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
except AnsibleError:
raise
ports = [
{
"name": port.name,
"port": port.port,
"protocol": port.protocol,
"targetPort": port.targetPort,
"nodePort": port.nodePort,
}
for port in service.spec.ports or []
]
ports = [{'name': port.name,
'port': port.port,
'protocol': port.protocol,
'targetPort': port.targetPort,
'nodePort': port.nodePort} for port in service.spec.ports or []]
# add hostvars
self.inventory.set_variable(service_name, "object_type", "service")
self.inventory.set_variable(service_name, "labels", service_labels)
self.inventory.set_variable(
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(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)
self.inventory.set_variable(service_name, 'object_type', 'service')
self.inventory.set_variable(service_name, 'labels', service_labels)
self.inventory.set_variable(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(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:
self.inventory.set_variable(
service_name,
"external_traffic_policy",
service.spec.externalTrafficPolicy,
)
self.inventory.set_variable(service_name, 'external_traffic_policy',
service.spec.externalTrafficPolicy)
if service.spec.externalIPs:
self.inventory.set_variable(
service_name, "external_ips", service.spec.externalIPs
)
self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs)
if service.spec.externalName:
self.inventory.set_variable(
service_name, "external_name", service.spec.externalName
)
self.inventory.set_variable(service_name, 'external_name', service.spec.externalName)
if service.spec.healthCheckNodePort:
self.inventory.set_variable(
service_name,
"health_check_node_port",
service.spec.healthCheckNodePort,
)
self.inventory.set_variable(service_name, 'health_check_node_port',
service.spec.healthCheckNodePort)
if service.spec.loadBalancerIP:
self.inventory.set_variable(
service_name, "load_balancer_ip", service.spec.loadBalancerIP
)
self.inventory.set_variable(service_name, 'load_balancer_ip',
service.spec.loadBalancerIP)
if service.spec.selector:
self.inventory.set_variable(
service_name, "selector", dict(service.spec.selector)
)
self.inventory.set_variable(service_name, 'selector', dict(service.spec.selector))
if (
hasattr(service.status.loadBalancer, "ingress")
and service.status.loadBalancer.ingress
):
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
)
if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress:
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)
from __future__ import absolute_import, division, print_function
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: k8s
DOCUMENTATION = '''
lookup: k8s
short_description: Query the K8s API
author:
- Chris Houseknecht (@chouseknecht)
- Fabian von Feilitzsch (@fabianvf)
- Chris Houseknecht <@chouseknecht>
- Fabian von Feilitzsch <@fabianvf>
description:
- 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.
- Provides access the full range of K8s APIs.
- 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:
cluster_info:
description:
@@ -114,31 +111,31 @@ DOCUMENTATION = """
aliases: [ verify_ssl ]
requirements:
- "python >= 3.9"
- "kubernetes >= 24.2.0"
- "python >= 3.6"
- "kubernetes >= 12.0.0"
- "PyYAML >= 3.11"
"""
'''
EXAMPLES = """
- name: Fetch a list of namespaces
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
set_fact:
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
- name: Fetch all deployments in a namespace
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
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
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
@@ -148,62 +145,50 @@ EXAMPLES = """
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
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
set_fact:
service: "{{ query('kubernetes.core.k8s', src='service.yml') }}"
service: "{{ lookup('kubernetes.core.k8s', src='service.yml') }}"
"""
RETURN = """
_list:
description:
- One ore more object definitions returned from the API.
type: list
elements: dict
sample:
- kind: ConfigMap
apiVersion: v1
metadata:
creationTimestamp: "2022-03-04T13:59:49Z"
name: my-config-map
namespace: default
resourceVersion: "418"
uid: 5714b011-d090-4eac-8272-a0ea82ec0abd
data:
key1: val1
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 os
from ansible.errors import AnsibleError
from ansible.module_utils.common._collections_compat import KeysView
from ansible.module_utils.common.validation import check_type_bool
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
create_definitions,
)
from ansible.plugins.lookup import LookupBase
try:
enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
except TypeError:
enable_turbo_mode = False
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
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:
from kubernetes.dynamic.exceptions import NotFoundError
HAS_K8S_MODULE_HELPER = True
k8s_import_exception = None
except ImportError as e:
@@ -211,13 +196,13 @@ except ImportError as e:
k8s_import_exception = e
class KubernetesLookup(object):
class KubernetesLookup(K8sAnsibleMixin):
def __init__(self):
if not HAS_K8S_MODULE_HELPER:
raise Exception(
"Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
k8s_import_exception
)
"Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(k8s_import_exception)
)
self.kind = None
@@ -238,38 +223,31 @@ class KubernetesLookup(object):
self.params = kwargs
self.client = get_api_client(**kwargs)
cluster_info = kwargs.get("cluster_info")
if cluster_info == "version":
return [self.client.client.version]
if cluster_info == "api_groups":
cluster_info = kwargs.get('cluster_info')
if cluster_info == 'version':
return [self.client.version]
if cluster_info == 'api_groups':
if isinstance(self.client.resources.api_groups, KeysView):
return [list(self.client.resources.api_groups)]
return [self.client.resources.api_groups]
self.kind = kwargs.get("kind")
self.name = kwargs.get("resource_name")
self.namespace = kwargs.get("namespace")
self.api_version = kwargs.get("api_version", "v1")
self.label_selector = kwargs.get("label_selector")
self.field_selector = kwargs.get("field_selector")
self.include_uninitialized = kwargs.get("include_uninitialized", False)
self.kind = kwargs.get('kind')
self.name = kwargs.get('resource_name')
self.namespace = kwargs.get('namespace')
self.api_version = kwargs.get('api_version', 'v1')
self.label_selector = kwargs.get('label_selector')
self.field_selector = kwargs.get('field_selector')
self.include_uninitialized = kwargs.get('include_uninitialized', False)
resource_definition = kwargs.get("resource_definition")
src = kwargs.get("src")
resource_definition = kwargs.get('resource_definition')
src = kwargs.get('src')
if src:
definitions = create_definitions(params=dict(src=src))
if definitions:
self.kind = definitions[0].kind
self.name = definitions[0].name
self.namespace = definitions[0].namespace
self.api_version = definitions[0].api_version or "v1"
resource_definition = self.load_resource_definitions(src)[0]
if resource_definition:
self.kind = resource_definition.get("kind", self.kind)
self.api_version = resource_definition.get("apiVersion", self.api_version)
self.name = resource_definition.get("metadata", {}).get("name", self.name)
self.namespace = resource_definition.get("metadata", {}).get(
"namespace", self.namespace
)
self.kind = resource_definition.get('kind', self.kind)
self.api_version = resource_definition.get('apiVersion', self.api_version)
self.name = resource_definition.get('metadata', {}).get('name', self.name)
self.namespace = resource_definition.get('metadata', {}).get('namespace', self.namespace)
if not self.kind:
raise AnsibleError(
@@ -277,26 +255,19 @@ class KubernetesLookup(object):
"using the 'resource_definition' parameter."
)
resource = self.client.resource(self.kind, self.api_version)
resource = self.find_resource(self.kind, self.api_version, fail=True)
try:
params = dict(
name=self.name,
namespace=self.namespace,
label_selector=self.label_selector,
field_selector=self.field_selector,
)
k8s_obj = self.client.get(resource, **params)
k8s_obj = resource.get(name=self.name, namespace=self.namespace, label_selector=self.label_selector, field_selector=self.field_selector)
except NotFoundError:
return []
if self.name:
return [k8s_obj.to_dict()]
return k8s_obj.to_dict().get("items")
return [k8s_obj.to_dict().get('items')]
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,148 +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.
enable_helm:
description:
- Enable the helm chart inflation generator
default: "False"
requirements:
- "python >= 3.6"
"""
EXAMPLES = """
- name: Run lookup using kustomize
ansible.builtin.set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
- name: Run lookup using kubectl kustomize
ansible.builtin.set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
- name: Create kubernetes resources for lookup output
kubernetes.core.k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
- name: Create kubernetes resources for lookup output with `--enable-helm` set
kubernetes.core.k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization', enable_helm=True) }}"
"""
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
"""
import subprocess
from ansible.errors import AnsibleLookupError
from ansible.module_utils.common.process import get_bin_path
from ansible.plugins.lookup import LookupBase
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,
enable_helm=False,
**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
)
)
if enable_helm:
command += ["--enable-helm"]
(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 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
import os
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 ( # noqa: F401
AnsibleTurboModule as AnsibleModule,
)
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
from ansible.module_utils.basic import AnsibleModule # noqa: F401

View File

@@ -14,22 +14,13 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from collections import OrderedDict
import json
from ansible.module_utils.common.dict_transformations import dict_merge
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
ApplyException,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
gather_versions,
)
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
try:
from kubernetes.dynamic.exceptions import NotFoundError
@@ -37,52 +28,50 @@ except ImportError:
pass
LAST_APPLIED_CONFIG_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
POD_SPEC_SUFFIXES = {
"containers": "name",
"initContainers": "name",
"ephemeralContainers": "name",
"volumes": "name",
"imagePullSecrets": "name",
"containers.volumeMounts": "mountPath",
"containers.volumeDevices": "devicePath",
"containers.env": "name",
"containers.ports": "containerPort",
"initContainers.volumeMounts": "mountPath",
"initContainers.volumeDevices": "devicePath",
"initContainers.env": "name",
"initContainers.ports": "containerPort",
"ephemeralContainers.volumeMounts": "mountPath",
"ephemeralContainers.volumeDevices": "devicePath",
"ephemeralContainers.env": "name",
"ephemeralContainers.ports": "containerPort",
'containers': 'name',
'initContainers': 'name',
'ephemeralContainers': 'name',
'volumes': 'name',
'imagePullSecrets': 'name',
'containers.volumeMounts': 'mountPath',
'containers.volumeDevices': 'devicePath',
'containers.env': 'name',
'containers.ports': 'containerPort',
'initContainers.volumeMounts': 'mountPath',
'initContainers.volumeDevices': 'devicePath',
'initContainers.env': 'name',
'initContainers.ports': 'containerPort',
'ephemeralContainers.volumeMounts': 'mountPath',
'ephemeralContainers.volumeDevices': 'devicePath',
'ephemeralContainers.env': 'name',
'ephemeralContainers.ports': 'containerPort',
}
POD_SPEC_PREFIXES = [
"Pod.spec",
"Deployment.spec.template.spec",
"DaemonSet.spec.template.spec",
"StatefulSet.spec.template.spec",
"Job.spec.template.spec",
"Cronjob.spec.jobTemplate.spec.template.spec",
'Pod.spec',
'Deployment.spec.template.spec',
'DaemonSet.spec.template.spec',
'StatefulSet.spec.template.spec',
'Job.spec.template.spec',
'Cronjob.spec.jobTemplate.spec.template.spec',
]
# patch merge keys taken from generated.proto files under
# staging/src/k8s.io/api in kubernetes/kubernetes
STRATEGIC_MERGE_PATCH_KEYS = {
"Service.spec.ports": "port",
"ServiceAccount.secrets": "name",
"ValidatingWebhookConfiguration.webhooks": "name",
"MutatingWebhookConfiguration.webhooks": "name",
'Service.spec.ports': 'port',
'ServiceAccount.secrets': 'name',
'ValidatingWebhookConfiguration.webhooks': 'name',
'MutatingWebhookConfiguration.webhooks': 'name',
}
STRATEGIC_MERGE_PATCH_KEYS.update(
{
"%s.%s" % (prefix, key): value
for prefix in POD_SPEC_PREFIXES
for key, value in POD_SPEC_SUFFIXES.items()
}
{"%s.%s" % (prefix, key): value
for prefix in POD_SPEC_PREFIXES
for key, value in POD_SPEC_SUFFIXES.items()}
)
@@ -90,28 +79,21 @@ def annotate(desired):
return dict(
metadata=dict(
annotations={
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(
desired, separators=(",", ":"), indent=None, sort_keys=True
)
LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True)
}
)
)
def apply_patch(actual, desired):
last_applied = (
actual["metadata"].get("annotations", {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
)
last_applied = actual['metadata'].get('annotations', {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
if last_applied:
# 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
last_applied = json.loads(last_applied)
patch = merge(
dict_merge(last_applied, annotate(last_applied)),
dict_merge(desired, annotate(desired)),
actual,
)
patch = merge(dict_merge(last_applied, annotate(last_applied)),
dict_merge(desired, annotate(desired)), actual)
if patch:
return actual, patch
else:
@@ -120,54 +102,24 @@ def apply_patch(actual, desired):
return actual, dict_merge(desired, annotate(desired))
def apply_object(resource, definition, server_side=False):
def apply_object(resource, definition):
try:
actual = resource.get(
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
)
if server_side:
return actual, None
actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
except NotFoundError:
return None, dict_merge(definition, annotate(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)
server_side = kwargs.get("server_side", False)
if server_side:
versions = gather_versions()
body = definition
if LooseVersion(versions["kubernetes"]) < LooseVersion("25.0.0"):
body = json.dumps(definition).encode()
# server_side_apply is forces content_type to 'application/apply-patch+yaml'
return resource.server_side_apply(
body=body,
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
force_conflicts=kwargs.get("force_conflicts"),
field_manager=kwargs.get("field_manager"),
dry_run=kwargs.get("dry_run"),
serialize=kwargs.get("serialize"),
)
if not existing:
return resource.create(
body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
)
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
if existing == desired:
return resource.get(
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
**kwargs
)
return resource.patch(
body=desired,
name=definition["metadata"]["name"],
namespace=definition["metadata"].get("namespace"),
content_type="application/merge-patch+json",
**kwargs
)
return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
return resource.patch(body=desired,
name=definition['metadata']['name'],
namespace=definition['metadata'].get('namespace'),
content_type='application/merge-patch+json')
# The patch is the difference from actual to desired without deletions, plus deletions
@@ -176,7 +128,7 @@ def k8s_apply(resource, definition, **kwargs):
# deletions, and then apply delta to deletions as a patch, which should be strictly additive.
def merge(last_applied, desired, actual, position=None):
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)
@@ -186,9 +138,7 @@ def list_to_dict(lst, key, position):
try:
result[item[key]] = item
except KeyError:
raise ApplyException(
"Expected key '%s' not found in position %s" % (key, position)
)
raise ApplyException("Expected key '%s' not found in position %s" % (key, position))
return result
@@ -207,12 +157,7 @@ def list_merge(last_applied, actual, desired, position):
if key not in actual_dict or key not in last_applied_dict:
result.append(desired_dict[key])
else:
patch = merge(
last_applied_dict[key],
desired_dict[key],
actual_dict[key],
position,
)
patch = merge(last_applied_dict[key], desired_dict[key], actual_dict[key], position)
result.append(dict_merge(actual_dict[key], patch))
for key in actual_dict:
if key not in desired_dict and key not in last_applied_dict:
@@ -252,11 +197,11 @@ def recursive_list_diff(list1, list2, position=None):
def recursive_diff(dict1, dict2, position=None):
if not position:
if "kind" in dict1 and dict1.get("kind") == dict2.get("kind"):
position = dict1["kind"]
if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
position = dict1['kind']
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)
for k in set(dict1.keys()) & set(dict2.keys()):
for k in (set(dict1.keys()) & set(dict2.keys())):
if position:
this_position = "%s.%s" % (position, k)
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
@@ -301,15 +246,11 @@ def get_delta(last_applied, actual, desired, position=None):
if actual_value is None:
patch[k] = desired_value
elif isinstance(desired_value, dict):
p = get_delta(
last_applied.get(k, {}), actual_value, desired_value, this_position
)
p = get_delta(last_applied.get(k, {}), actual_value, desired_value, this_position)
if p:
patch[k] = p
elif isinstance(desired_value, list):
p = list_merge(
last_applied.get(k, []), actual_value, desired_value, this_position
)
p = list_merge(last_applied.get(k, []), actual_value, desired_value, this_position)
if p:
patch[k] = [item for item in p if item is not None]
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
@@ -12,87 +12,133 @@ def list_dict_str(value):
AUTH_PROXY_HEADERS_SPEC = dict(
proxy_basic_auth=dict(type="str", no_log=True),
basic_auth=dict(type="str", no_log=True),
user_agent=dict(type="str"),
proxy_basic_auth=dict(type='str', no_log=True),
basic_auth=dict(type='str', no_log=True),
user_agent=dict(type='str')
)
AUTH_ARG_SPEC = {
"kubeconfig": {"type": "raw"},
"context": {},
"host": {},
"api_key": {"no_log": True},
"username": {},
"password": {"no_log": True},
"validate_certs": {"type": "bool", "aliases": ["verify_ssl"]},
"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"},
"no_proxy": {"type": "str"},
"proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC},
"persist_config": {"type": "bool"},
"impersonate_user": {},
"impersonate_groups": {"type": "list", "elements": "str"},
'kubeconfig': {
'type': 'path',
},
'context': {},
'host': {},
'api_key': {
'no_log': True,
},
'username': {},
'password': {
'no_log': True,
},
'validate_certs': {
'type': 'bool',
'aliases': ['verify_ssl'],
},
'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=dict(type="bool", default=False),
wait_sleep=dict(type="int", default=5),
wait_timeout=dict(type="int", default=120),
wait=dict(type='bool', default=False),
wait_sleep=dict(type='int', default=5),
wait_timeout=dict(type='int', default=120),
wait_condition=dict(
type="dict",
type='dict',
default=None,
options=dict(
type=dict(),
status=dict(default=True, choices=[True, False, "Unknown"]),
reason=dict(),
),
),
reason=dict()
)
)
)
# Map kubernetes-client parameters to ansible parameters
AUTH_ARG_MAP = {
"kubeconfig": "kubeconfig",
"context": "context",
"host": "host",
"api_key": "api_key",
"username": "username",
"password": "password",
"verify_ssl": "validate_certs",
"ssl_ca_cert": "ca_cert",
"cert_file": "client_cert",
"key_file": "client_key",
"proxy": "proxy",
"no_proxy": "no_proxy",
"proxy_headers": "proxy_headers",
"persist_config": "persist_config",
'kubeconfig': 'kubeconfig',
'context': 'context',
'host': 'host',
'api_key': 'api_key',
'username': 'username',
'password': 'password',
'verify_ssl': 'validate_certs',
'ssl_ca_cert': 'ca_cert',
'cert_file': 'client_cert',
'key_file': 'client_key',
'proxy': 'proxy',
'proxy_headers': 'proxy_headers',
'persist_config': 'persist_config',
}
NAME_ARG_SPEC = {
"kind": {},
"name": {},
"namespace": {},
"api_version": {"default": "v1", "aliases": ["api", "version"]},
'kind': {},
'name': {},
'namespace': {},
'api_version': {
'default': 'v1',
'aliases': ['api', 'version'],
},
}
COMMON_ARG_SPEC = {
"state": {"default": "present", "choices": ["present", "absent"]},
"force": {"type": "bool", "default": False},
'state': {
'default': 'present',
'choices': ['present', 'absent'],
},
'force': {
'type': 'bool',
'default': False,
},
}
RESOURCE_ARG_SPEC = {
"resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]},
"src": {"type": "path"},
}
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"}},
'resource_definition': {
'type': list_dict_str,
'aliases': ['definition', 'inline']
},
'src': {
'type': 'path',
},
}
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

@@ -13,35 +13,26 @@
# limitations under the License.
import hashlib
import json
import os
import tempfile
from collections import defaultdict
from functools import partial
import hashlib
import tempfile
import kubernetes.dynamic
import kubernetes.dynamic.discovery
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
ResourceList,
)
from kubernetes import __version__
from kubernetes.dynamic.exceptions import (
ResourceNotFoundError,
ResourceNotUniqueError,
ServiceUnavailableError,
)
from kubernetes.dynamic.exceptions import (ResourceNotFoundError, ResourceNotUniqueError,
ServiceUnavailableError)
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
class Discoverer(kubernetes.dynamic.discovery.Discoverer):
def __init__(self, client, cache_file):
self.client = client
default_cache_file_name = "k8srcp-{0}.json".format(
hashlib.sha256(self.__get_default_cache_id()).hexdigest()
)
self.__cache_file = cache_file or os.path.join(
tempfile.gettempdir(), default_cache_file_name
)
default_cache_file_name = 'k8srcp-{0}.json'.format(hashlib.sha256(self.__get_default_cache_id()).hexdigest())
self.__cache_file = cache_file or os.path.join(tempfile.gettempdir(), default_cache_file_name)
self.__init_cache()
def __get_default_cache_id(self):
@@ -50,21 +41,21 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
cache_id = "{0}-{1}".format(self.client.configuration.host, user)
else:
cache_id = self.client.configuration.host
return cache_id.encode("utf-8")
return cache_id.encode('utf-8')
def __get_user(self):
# This is intended to provide a portable method for getting a username.
# 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
# place.
if hasattr(os, "getlogin"):
if hasattr(os, 'getlogin'):
try:
user = os.getlogin()
if user:
return str(user)
except OSError:
pass
if hasattr(os, "getuid"):
if hasattr(os, 'getuid'):
try:
user = os.getuid()
if user:
@@ -78,13 +69,13 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
def __init_cache(self, refresh=False):
if refresh or not os.path.exists(self.__cache_file):
self._cache = {"library_version": __version__}
self._cache = {'library_version': __version__}
refresh = True
else:
try:
with open(self.__cache_file, "r") as f:
self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
if self._cache.get("library_version") != __version__:
with open(self.__cache_file, 'r') as f:
self._cache = json.load(f, cls=CacheDecoder(self.client))
if self._cache.get('library_version') != __version__:
# Version mismatch, need to refresh cache
self.invalidate_cache()
except Exception:
@@ -95,30 +86,26 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
self._write_cache()
def get_resources_for_api_version(self, prefix, group, version, preferred):
"""returns a dictionary of resources associated with provided (prefix, group, version)"""
""" returns a dictionary of resources associated with provided (prefix, group, version)"""
resources = defaultdict(list)
subresources = defaultdict(dict)
path = "/".join(filter(None, [prefix, group, version]))
path = '/'.join(filter(None, [prefix, group, version]))
try:
resources_response = self.client.request("GET", path).resources or []
resources_response = self.client.request('GET', path).resources or []
except ServiceUnavailableError:
resources_response = []
resources_raw = list(
filter(lambda resource: "/" not in resource["name"], resources_response)
)
subresources_raw = list(
filter(lambda resource: "/" in resource["name"], resources_response)
)
resources_raw = list(filter(lambda resource: '/' not in resource['name'], resources_response))
subresources_raw = list(filter(lambda resource: '/' in resource['name'], resources_response))
for subresource in subresources_raw:
resource, name = subresource["name"].split("/", 1)
resource, name = subresource['name'].split('/')
subresources[resource][name] = subresource
for resource in resources_raw:
# 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)
resourceobj = kubernetes.dynamic.Resource(
@@ -127,25 +114,19 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
api_version=version,
client=self.client,
preferred=preferred,
subresources=subresources.get(resource["name"]),
subresources=subresources.get(resource['name']),
**resource
)
resources[resource["kind"]].append(resourceobj)
resources[resource['kind']].append(resourceobj)
resource_lookup = {
"prefix": prefix,
"group": group,
"api_version": version,
"kind": resourceobj.kind,
"name": resourceobj.name,
'prefix': prefix,
'group': group,
'api_version': version,
'kind': resourceobj.kind,
'name': resourceobj.name
}
resource_list = ResourceList(
self.client,
group=group,
api_version=version,
base_kind=resource["kind"],
base_resource_lookup=resource_lookup,
)
resource_list = ResourceList(self.client, group=group, api_version=version, base_kind=resource['kind'], base_resource_lookup=resource_lookup)
resources[resource_list.kind].append(resource_list)
return resources
@@ -157,32 +138,23 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
"""
results = self.search(**kwargs)
# 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 = [
result
for result in results
if result.group_version == kwargs["api_version"]
result for result in results if result.group_version == kwargs['api_version']
]
# If there are multiple matches, prefer non-List kinds
if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results):
results = [
result for result in results if not isinstance(result, ResourceList)
]
if len(results) > 1 and not all([isinstance(x, ResourceList) for x in results]):
results = [result for result in results if not isinstance(result, ResourceList)]
# if multiple resources are found that share a GVK, prefer the one with the most supported verbs
if (
len(results) > 1
and len(set((x.group_version, x.kind) for x in results)) == 1
):
if len(results) > 1 and len(set((x.group_version, x.kind) for x in results)) == 1:
if len(set(len(x.verbs) for x in results)) != 1:
results = [max(results, key=lambda x: len(x.verbs))]
if len(results) == 1:
return results[0]
elif not results:
raise ResourceNotFoundError("No matches found for {0}".format(kwargs))
raise ResourceNotFoundError('No matches found for {0}'.format(kwargs))
else:
raise ResourceNotUniqueError(
"Multiple matches found for {0}: {1}".format(kwargs, results)
)
raise ResourceNotUniqueError('Multiple matches found for {0}: {1}'.format(kwargs, results))
class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
@@ -190,10 +162,6 @@ class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
Discoverer.__init__(self, client, cache_file)
self.__update_cache = False
@property
def update_cache(self):
self.__update_cache
class CacheDecoder(json.JSONDecoder):
def __init__(self, client, *args, **kwargs):
@@ -201,15 +169,13 @@ class CacheDecoder(json.JSONDecoder):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, obj):
if "_type" not in obj:
if '_type' not in obj:
return obj
_type = obj.pop("_type")
if _type == "Resource":
_type = obj.pop('_type')
if _type == 'Resource':
return kubernetes.dynamic.Resource(client=self.client, **obj)
elif _type == "ResourceList":
elif _type == 'ResourceList':
return ResourceList(self.client, **obj)
elif _type == "ResourceGroup":
return kubernetes.dynamic.discovery.ResourceGroup(
obj["preferred"], resources=self.object_hook(obj["resources"])
)
elif _type == 'ResourceGroup':
return kubernetes.dynamic.discovery.ResourceGroup(obj['preferred'], resources=self.object_hook(obj['resources']))
return obj

View File

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

View File

@@ -16,50 +16,866 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import time
import os
from tempfile import NamedTemporaryFile
import traceback
import sys
import hashlib
from datetime import datetime
from distutils.version import LooseVersion
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.urls import Request
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_MAP, AUTH_ARG_SPEC, AUTH_PROXY_HEADERS_SPEC)
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import generate_hash
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.six import iteritems, string_types
from ansible.module_utils._text import to_native, to_bytes, to_text
from ansible.module_utils.common.dict_transformations import dict_merge
from ansible.module_utils.parsing.convert_bool import boolean
K8S_IMP_ERR = None
try:
import kubernetes
from kubernetes.dynamic.exceptions import (
NotFoundError, ResourceNotFoundError, ResourceNotUniqueError, DynamicApiError,
ConflictError, ForbiddenError, MethodNotAllowedError, BadRequestError,
KubernetesValidateMissing
)
HAS_K8S_MODULE_HELPER = True
k8s_import_exception = None
except ImportError as e:
HAS_K8S_MODULE_HELPER = False
k8s_import_exception = e
K8S_IMP_ERR = traceback.format_exc()
IMP_K8S_CLIENT = None
try:
from ansible_collections.kubernetes.core.plugins.module_utils.k8sdynamicclient import K8SDynamicClient
from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import LazyDiscoverer
IMP_K8S_CLIENT = True
except ImportError as e:
IMP_K8S_CLIENT = False
k8s_client_import_exception = e
IMP_K8S_CLIENT_ERR = traceback.format_exc()
YAML_IMP_ERR = None
try:
import yaml
HAS_YAML = True
except ImportError:
YAML_IMP_ERR = traceback.format_exc()
HAS_YAML = False
HAS_K8S_APPLY = None
try:
from ansible_collections.kubernetes.core.plugins.module_utils.apply import apply_object
HAS_K8S_APPLY = True
except ImportError:
HAS_K8S_APPLY = False
try:
import urllib3
urllib3.disable_warnings()
except ImportError:
pass
try:
from ansible_collections.kubernetes.core.plugins.module_utils.apply import recursive_diff
except ImportError:
from ansible.module_utils.common.dict_transformations import recursive_diff
def fetch_file_from_url(module, url):
# Download file
bufsize = 65536
file_name, file_ext = os.path.splitext(str(url.rsplit("/", 1)[1]))
temp_file = NamedTemporaryFile(
dir=module.tmpdir, prefix=file_name, suffix=file_ext, delete=False
)
module.add_cleanup_file(temp_file.name)
try:
from kubernetes.dynamic.resource import ResourceInstance
HAS_K8S_INSTANCE_HELPER = True
k8s_import_exception = None
except ImportError as e:
HAS_K8S_INSTANCE_HELPER = False
k8s_import_exception = e
K8S_IMP_ERR = traceback.format_exc()
def configuration_digest(configuration):
m = hashlib.sha256()
for k in AUTH_ARG_MAP:
if not hasattr(configuration, k):
v = None
else:
v = getattr(configuration, k)
if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
with open(str(v), "r") as fd:
content = fd.read()
m.update(content.encode())
else:
m.update(str(v).encode())
digest = m.hexdigest()
return digest
def get_api_client(module=None, **kwargs):
auth = {}
def _raise_or_fail(exc, msg):
if module:
module.fail_json(msg % to_native(exc))
raise exc
# If authorization variables aren't defined, look for them in environment variables
for true_name, arg_name in AUTH_ARG_MAP.items():
if module and module.params.get(arg_name) is not None:
auth[true_name] = module.params.get(arg_name)
elif arg_name in kwargs and kwargs.get(arg_name) is not None:
auth[true_name] = kwargs.get(arg_name)
elif arg_name == "proxy_headers":
# specific case for 'proxy_headers' which is a dictionary
proxy_headers = {}
for key in AUTH_PROXY_HEADERS_SPEC.keys():
env_value = os.getenv('K8S_AUTH_PROXY_HEADERS_{0}'.format(key.upper()), None)
if env_value is not None:
if AUTH_PROXY_HEADERS_SPEC[key].get('type') == 'bool':
env_value = env_value.lower() not in ['0', 'false', 'no']
proxy_headers[key] = env_value
if proxy_headers is not {}:
auth[true_name] = proxy_headers
else:
env_value = os.getenv('K8S_AUTH_{0}'.format(arg_name.upper()), None) or os.getenv('K8S_AUTH_{0}'.format(true_name.upper()), None)
if env_value is not None:
if AUTH_ARG_SPEC[arg_name].get('type') == 'bool':
env_value = env_value.lower() not in ['0', 'false', 'no']
auth[true_name] = env_value
def auth_set(*names):
return all([auth.get(name) for name in names])
if auth_set('host'):
# Removing trailing slashes if any from hostname
auth['host'] = auth.get('host').rstrip('/')
if auth_set('username', 'password', 'host') or auth_set('api_key', 'host'):
# We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
pass
elif auth_set('kubeconfig') or auth_set('context'):
try:
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
except Exception as err:
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
else:
# First try to do incluster config, then kubeconfig
try:
kubernetes.config.load_incluster_config()
except kubernetes.config.ConfigException:
try:
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
except Exception as err:
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
# Override any values in the default configuration with Ansible parameters
# As of kubernetes-client v12.0.0, get_default_copy() is required here
try:
rsp = Request().open("GET", url)
if not rsp:
module.fail_json(msg="Failure downloading %s" % url)
data = rsp.read(bufsize)
while data:
temp_file.write(data)
data = rsp.read(bufsize)
temp_file.close()
except Exception as e:
module.fail_json(msg="Failure downloading %s, %s" % (url, to_native(e)))
return temp_file.name
configuration = kubernetes.client.Configuration().get_default_copy()
except AttributeError:
configuration = kubernetes.client.Configuration()
for key, value in iteritems(auth):
if key in AUTH_ARG_MAP.keys() and value is not None:
if key == 'api_key':
setattr(configuration, key, {'authorization': "Bearer {0}".format(value)})
elif key == 'proxy_headers':
headers = urllib3.util.make_headers(**value)
setattr(configuration, key, headers)
else:
setattr(configuration, key, value)
digest = configuration_digest(configuration)
if digest in get_api_client._pool:
client = get_api_client._pool[digest]
return client
try:
client = K8SDynamicClient(kubernetes.client.ApiClient(configuration), discoverer=LazyDiscoverer)
except Exception as err:
_raise_or_fail(err, 'Failed to get client due to %s')
get_api_client._pool[digest] = client
return client
get_api_client._pool = {}
class K8sAnsibleMixin(object):
def __init__(self, module, *args, **kwargs):
if not HAS_K8S_MODULE_HELPER:
module.fail_json(msg=missing_required_lib('kubernetes'), exception=K8S_IMP_ERR,
error=to_native(k8s_import_exception))
self.kubernetes_version = kubernetes.__version__
if not HAS_YAML:
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
def find_resource(self, kind, api_version, fail=False):
for attribute in ['kind', 'name', 'singular_name']:
try:
return self.client.resources.get(**{'api_version': api_version, attribute: kind})
except (ResourceNotFoundError, ResourceNotUniqueError):
pass
try:
return self.client.resources.get(api_version=api_version, short_names=[kind])
except (ResourceNotFoundError, ResourceNotUniqueError):
if fail:
self.fail(msg='Failed to find exact match for {0}.{1} by [kind, name, singularName, shortNames]'.format(api_version, kind))
def kubernetes_facts(self, kind, api_version, name=None, namespace=None, label_selectors=None, field_selectors=None,
wait=False, wait_sleep=5, wait_timeout=120, state='present', condition=None):
resource = self.find_resource(kind, api_version)
api_found = bool(resource)
if not api_found:
return dict(resources=[], msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(api_version, kind), api_found=False)
if not label_selectors:
label_selectors = []
if not field_selectors:
field_selectors = []
result = None
try:
result = resource.get(name=name, namespace=namespace,
label_selector=','.join(label_selectors),
field_selector=','.join(field_selectors))
except BadRequestError:
return dict(resources=[], api_found=True)
except NotFoundError:
if not wait or name is None:
return dict(resources=[], api_found=True)
if not wait:
result = result.to_dict()
if 'items' in result:
return dict(resources=result['items'], api_found=True)
return dict(resources=[result], api_found=True)
start = datetime.now()
def _elapsed():
return (datetime.now() - start).seconds
if result is None:
while _elapsed() < wait_timeout:
try:
result = resource.get(name=name, namespace=namespace,
label_selector=','.join(label_selectors),
field_selector=','.join(field_selectors))
break
except NotFoundError:
pass
time.sleep(wait_sleep)
if result is None:
return dict(resources=[], api_found=True)
if isinstance(result, ResourceInstance):
satisfied_by = []
# We have a list of ResourceInstance
resource_list = result.get('items', [])
if not resource_list:
resource_list = [result]
for resource_instance in resource_list:
success, res, duration = self.wait(resource, resource_instance,
sleep=wait_sleep, timeout=wait_timeout,
state=state, condition=condition)
if not success:
self.fail(msg="Failed to gather information about %s(s) even"
" after waiting for %s seconds" % (res.get('kind'), duration))
satisfied_by.append(res)
return dict(resources=satisfied_by, api_found=True)
result = result.to_dict()
if 'items' in result:
return dict(resources=result['items'], api_found=True)
return dict(resources=[result], api_found=True)
def remove_aliases(self):
"""
The helper doesn't know what to do with aliased keys
"""
for k, v in iteritems(self.argspec):
if 'aliases' in v:
for alias in v['aliases']:
if alias in self.params:
self.params.pop(alias)
def load_resource_definitions(self, src):
""" Load the requested src path """
result = None
path = os.path.normpath(src)
if not os.path.exists(path):
self.fail(msg="Error accessing {0}. Does the file exist?".format(path))
try:
with open(path, 'r') as f:
result = list(yaml.safe_load_all(f))
except (IOError, yaml.YAMLError) as exc:
self.fail(msg="Error loading resource_definition: {0}".format(exc))
return result
def diff_objects(self, existing, new):
result = dict()
diff = recursive_diff(existing, new)
if not diff:
return True, result
result['before'] = diff[0]
result['after'] = diff[1]
# If only metadata.generation and metadata.resourceVersion changed, ignore it
ignored_keys = set(['generation', 'resourceVersion'])
if list(result['after'].keys()) != ['metadata'] or list(result['before'].keys()) != ['metadata']:
return False, result
if not set(result['after']['metadata'].keys()).issubset(ignored_keys):
return False, result
if not set(result['before']['metadata'].keys()).issubset(ignored_keys):
return False, result
if hasattr(self, 'warn'):
self.warn('No meaningful diff was generated, but the API may not be idempotent (only metadata.generation or metadata.resourceVersion were changed)')
return True, result
def fail(self, msg=None):
self.fail_json(msg=msg)
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state):
start = datetime.now()
def _wait_for_elapsed():
return (datetime.now() - start).seconds
response = None
while _wait_for_elapsed() < timeout:
try:
response = resource.get(name=name, namespace=namespace)
if predicate(response):
if response:
return True, response.to_dict(), _wait_for_elapsed()
return True, {}, _wait_for_elapsed()
time.sleep(sleep)
except NotFoundError:
if state == 'absent':
return True, {}, _wait_for_elapsed()
if response:
response = response.to_dict()
return False, response, _wait_for_elapsed()
def wait(self, resource, definition, sleep, timeout, state='present', condition=None):
def _deployment_ready(deployment):
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
# deployment.status.replicas is None is perfectly ok if desired replicas == 0
# Scaling up means that we also need to check that we're not in a
# situation where status.replicas == status.availableReplicas
# but spec.replicas != status.replicas
return (deployment.status
and deployment.spec.replicas == (deployment.status.replicas or 0)
and deployment.status.availableReplicas == deployment.status.replicas
and deployment.status.observedGeneration == deployment.metadata.generation
and not deployment.status.unavailableReplicas)
def _pod_ready(pod):
return (pod.status and pod.status.containerStatuses is not None
and all([container.ready for container in pod.status.containerStatuses]))
def _daemonset_ready(daemonset):
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None
and daemonset.status.updatedNumberScheduled == daemonset.status.desiredNumberScheduled
and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled
and daemonset.status.observedGeneration == daemonset.metadata.generation
and not daemonset.status.unavailableReplicas)
def _custom_condition(resource):
if not resource.status or not resource.status.conditions:
return False
match = [x for x in resource.status.conditions if x.type == condition['type']]
if not match:
return False
# There should never be more than one condition of a specific type
match = match[0]
if match.status == 'Unknown':
if match.status == condition['status']:
if 'reason' not in condition:
return True
if condition['reason']:
return match.reason == condition['reason']
return False
status = True if match.status == 'True' else False
if status == boolean(condition['status'], strict=False):
if condition.get('reason'):
return match.reason == condition['reason']
return True
return False
def _resource_absent(resource):
return not resource
waiter = dict(
Deployment=_deployment_ready,
DaemonSet=_daemonset_ready,
Pod=_pod_ready
)
kind = definition['kind']
if state == 'present' and not condition:
predicate = waiter.get(kind, lambda x: x)
elif state == 'present' and condition:
predicate = _custom_condition
else:
predicate = _resource_absent
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
def set_resource_definitions(self, module):
resource_definition = module.params.get('resource_definition')
self.resource_definitions = []
if resource_definition:
if isinstance(resource_definition, string_types):
try:
self.resource_definitions = yaml.safe_load_all(resource_definition)
except (IOError, yaml.YAMLError) as exc:
self.fail(msg="Error loading resource_definition: {0}".format(exc))
elif isinstance(resource_definition, list):
for resource in resource_definition:
if isinstance(resource, string_types):
yaml_data = yaml.safe_load_all(resource)
for item in yaml_data:
if item is not None:
self.resource_definitions.append(item)
else:
self.resource_definitions.append(resource)
else:
self.resource_definitions = [resource_definition]
src = module.params.get('src')
if src:
self.resource_definitions = self.load_resource_definitions(src)
try:
self.resource_definitions = [item for item in self.resource_definitions if item]
except AttributeError:
pass
if not resource_definition and not src:
implicit_definition = dict(
kind=module.params['kind'],
apiVersion=module.params['api_version'],
metadata=dict(name=module.params['name'])
)
if module.params.get('namespace'):
implicit_definition['metadata']['namespace'] = module.params.get('namespace')
self.resource_definitions = [implicit_definition]
def check_library_version(self):
if LooseVersion(self.kubernetes_version) < LooseVersion("12.0.0"):
self.fail_json(msg="kubernetes >= 12.0.0 is required")
def flatten_list_kind(self, list_resource, definitions):
flattened = []
parent_api_version = list_resource.group_version if list_resource else None
parent_kind = list_resource.kind[:-4] if list_resource else None
for definition in definitions.get('items', []):
resource = self.find_resource(definition.get('kind', parent_kind), definition.get('apiVersion', parent_api_version), fail=True)
flattened.append((resource, self.set_defaults(resource, definition)))
return flattened
def execute_module(self):
changed = False
results = []
try:
self.client = get_api_client(self.module)
# Hopefully the kubernetes client will provide its own exception class one day
except (urllib3.exceptions.RequestError) as e:
self.fail_json(msg="Couldn't connect to Kubernetes: %s" % str(e))
flattened_definitions = []
for definition in self.resource_definitions:
if definition is None:
continue
kind = definition.get('kind', self.kind)
api_version = definition.get('apiVersion', self.api_version)
if kind and kind.endswith('List'):
resource = self.find_resource(kind, api_version, fail=False)
flattened_definitions.extend(self.flatten_list_kind(resource, definition))
else:
resource = self.find_resource(kind, api_version, fail=True)
flattened_definitions.append((resource, definition))
for (resource, definition) in flattened_definitions:
kind = definition.get('kind', self.kind)
api_version = definition.get('apiVersion', self.api_version)
definition = self.set_defaults(resource, definition)
self.warnings = []
if self.params['validate'] is not None:
self.warnings = self.validate(definition)
result = self.perform_action(resource, definition)
if self.warnings:
result['warnings'] = self.warnings
changed = changed or result['changed']
results.append(result)
if len(results) == 1:
self.exit_json(**results[0])
self.exit_json(**{
'changed': changed,
'result': {
'results': results
}
})
def validate(self, resource):
def _prepend_resource_info(resource, msg):
return "%s %s: %s" % (resource['kind'], resource['metadata']['name'], msg)
try:
warnings, errors = self.client.validate(resource, self.params['validate'].get('version'), self.params['validate'].get('strict'))
except KubernetesValidateMissing:
self.fail_json(msg="kubernetes-validate python library is required to validate resources")
if errors and self.params['validate']['fail_on_error']:
self.fail_json(msg="\n".join([_prepend_resource_info(resource, error) for error in errors]))
else:
return [_prepend_resource_info(resource, msg) for msg in warnings + errors]
def set_defaults(self, resource, definition):
definition['kind'] = resource.kind
definition['apiVersion'] = resource.group_version
metadata = definition.get('metadata', {})
if self.name and not metadata.get('name'):
metadata['name'] = self.name
if resource.namespaced and self.namespace and not metadata.get('namespace'):
metadata['namespace'] = self.namespace
definition['metadata'] = metadata
return definition
def perform_action(self, resource, definition):
append_hash = self.params.get('append_hash', False)
apply = self.params.get('apply', False)
delete_options = self.params.get('delete_options')
result = {'changed': False, 'result': {}}
state = self.params.get('state', None)
force = self.params.get('force', False)
name = definition['metadata'].get('name')
origin_name = definition['metadata'].get('name')
namespace = definition['metadata'].get('namespace')
existing = None
wait = self.params.get('wait')
wait_sleep = self.params.get('wait_sleep')
wait_timeout = self.params.get('wait_timeout')
wait_condition = None
continue_on_error = self.params.get('continue_on_error')
if self.params.get('wait_condition') and self.params['wait_condition'].get('type'):
wait_condition = self.params['wait_condition']
def build_error_msg(kind, name, msg):
return "%s %s: %s" % (kind, name, msg)
self.remove_aliases()
try:
# ignore append_hash for resources other than ConfigMap and Secret
if append_hash and definition['kind'] in ['ConfigMap', 'Secret']:
name = '%s-%s' % (name, generate_hash(definition))
definition['metadata']['name'] = name
params = dict(name=name)
if namespace:
params['namespace'] = namespace
existing = resource.get(**params)
except (NotFoundError, MethodNotAllowedError):
# Remove traceback so that it doesn't show up in later failures
try:
sys.exc_clear()
except AttributeError:
# no sys.exc_clear on python3
pass
except ForbiddenError as exc:
if definition['kind'] in ['Project', 'ProjectRequest'] and state != 'absent':
return self.create_project_request(definition)
msg = 'Failed to retrieve requested object: {0}'.format(exc.body)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
except DynamicApiError as exc:
msg = 'Failed to retrieve requested object: {0}'.format(exc.body)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
except ValueError as value_exc:
msg = 'Failed to retrieve requested object: {0}'.format(to_native(value_exc))
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), error='', status='', reason='')
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error='', status='', reason='')
if state == 'absent':
result['method'] = "delete"
if not existing:
# The object already does not exist
return result
else:
# Delete the object
result['changed'] = True
if not self.check_mode:
if delete_options:
body = {
'apiVersion': 'v1',
'kind': 'DeleteOptions',
}
body.update(delete_options)
params['body'] = body
try:
k8s_obj = resource.delete(**params)
result['result'] = k8s_obj.to_dict()
except DynamicApiError as exc:
msg = "Failed to delete object: {0}".format(exc.body)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg),
error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
if wait:
success, resource, duration = self.wait(resource, definition, wait_sleep, wait_timeout, 'absent')
result['duration'] = duration
if not success:
msg = "Resource deletion timed out"
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
if apply:
if self.check_mode:
ignored, patch = apply_object(resource, _encode_stringdata(definition))
if existing:
k8s_obj = dict_merge(existing.to_dict(), patch)
else:
k8s_obj = patch
else:
try:
k8s_obj = resource.apply(definition, namespace=namespace).to_dict()
except DynamicApiError as exc:
msg = "Failed to apply object: {0}".format(exc.body)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'],
origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
success = True
result['result'] = k8s_obj
if wait and not self.check_mode:
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
if existing:
existing = existing.to_dict()
else:
existing = {}
match, diffs = self.diff_objects(existing, result['result'])
result['changed'] = not match
result['diff'] = diffs
result['method'] = 'apply'
if not success:
msg = "Resource apply timed out"
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
if not existing:
if state == 'patched':
# Silently skip this resource (do not raise an error) as 'patch_only' is set to true
result['changed'] = False
result['warning'] = "resource 'kind={kind},name={name}' was not found but will not be created as 'state'\
parameter has been set to '{state}'".format(
kind=definition['kind'], name=origin_name, state=state)
return result
elif self.check_mode:
k8s_obj = _encode_stringdata(definition)
else:
try:
k8s_obj = resource.create(definition, namespace=namespace).to_dict()
except ConflictError:
# Some resources, like ProjectRequests, can't be created multiple times,
# because the resources that they create don't match their kind
# In this case we'll mark it as unchanged and warn the user
self.warn("{0} was not found, but creating it returned a 409 Conflict error. This can happen \
if the resource you are creating does not directly create a resource of the same kind.".format(name))
return result
except DynamicApiError as exc:
msg = "Failed to create object: {0}".format(exc.body)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg),
error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=build_error_msg(definition['kind'], origin_name, msg), error=exc.status, status=exc.status, reason=exc.reason)
except Exception as exc:
msg = "Failed to create object: {0}".format(exc)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), error='', status='', reason='')
return result
else:
self.fail_json(msg=msg, error='', status='', reason='')
success = True
result['result'] = k8s_obj
if wait and not self.check_mode:
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
result['changed'] = True
result['method'] = 'create'
if not success:
msg = "Resource creation timed out"
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
self.fail_json(msg=msg, **result)
return result
match = False
diffs = []
if state == 'present' and existing and force:
if self.check_mode:
k8s_obj = _encode_stringdata(definition)
else:
try:
k8s_obj = resource.replace(definition, name=name, namespace=namespace, append_hash=append_hash).to_dict()
except DynamicApiError as exc:
msg = "Failed to replace object: {0}".format(exc.body)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg),
error=exc.status, status=exc.status, reason=exc.reason)
return result
else:
self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
success = True
result['result'] = k8s_obj
if wait and not self.check_mode:
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
result['changed'] = not match
result['method'] = 'replace'
result['diff'] = diffs
if not success:
msg = "Resource replacement timed out"
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
self.fail_json(msg=msg, **result)
return result
# Differences exist between the existing obj and requested params
if self.check_mode:
k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition))
else:
for merge_type in self.params['merge_type'] or ['strategic-merge', 'merge']:
k8s_obj, error = self.patch_resource(resource, definition, existing, name,
namespace, merge_type=merge_type)
if not error:
break
if error:
if continue_on_error:
result['error'] = error
result['error']['msg'] = build_error_msg(definition['kind'], origin_name, result['error'].get('msg'))
return result
else:
self.fail_json(**error)
success = True
result['result'] = k8s_obj
if wait and not self.check_mode:
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
result['changed'] = not match
result['method'] = 'patch'
result['diff'] = diffs
if not success:
msg = "Resource update timed out"
if continue_on_error:
result['error'] = dict(msg=build_error_msg(definition['kind'], origin_name, msg), **result)
return result
else:
self.fail_json(msg=msg, **result)
return result
def patch_resource(self, resource, definition, existing, name, namespace, merge_type=None):
if merge_type == "json":
self.module.deprecate(
msg="json as a merge_type value is deprecated. Please use the k8s_json_patch module instead.",
version="3.0.0", collection_name="kubernetes.core")
try:
params = dict(name=name, namespace=namespace)
if merge_type:
params['content_type'] = 'application/{0}-patch+json'.format(merge_type)
k8s_obj = resource.patch(definition, **params).to_dict()
match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
error = {}
return k8s_obj, {}
except DynamicApiError as exc:
msg = "Failed to patch object: {0}".format(exc.body)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
error = dict(msg=msg, error=exc.status, status=exc.status, reason=exc.reason, warnings=self.warnings)
return None, error
except Exception as exc:
msg = "Failed to patch object: {0}".format(exc)
if self.warnings:
msg += "\n" + "\n ".join(self.warnings)
error = dict(msg=msg, error=to_native(exc), status='', reason='', warnings=self.warnings)
return None, error
def create_project_request(self, definition):
definition['kind'] = 'ProjectRequest'
result = {'changed': False, 'result': {}}
resource = self.find_resource('ProjectRequest', definition['apiVersion'], fail=True)
if not self.check_mode:
try:
k8s_obj = resource.create(definition)
result['result'] = k8s_obj.to_dict()
except DynamicApiError as exc:
self.fail_json(msg="Failed to create object: {0}".format(exc.body),
error=exc.status, status=exc.status, reason=exc.reason)
result['changed'] = True
result['method'] = 'create'
return result
def _encode_stringdata(definition):
if definition["kind"] == "Secret" and "stringData" in definition:
for k, v in definition["stringData"].items():
if definition['kind'] == 'Secret' and 'stringData' in definition:
for k, v in definition['stringData'].items():
encoded = base64.b64encode(to_bytes(v))
definition.setdefault("data", {})[k] = to_text(encoded)
del definition["stringData"]
definition.setdefault('data', {})[k] = to_text(encoded)
del definition['stringData']
return definition

View File

@@ -1,445 +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
import tarfile
from abc import ABCMeta, abstractmethod
from select import select
from tempfile import NamedTemporaryFile, TemporaryFile
from ansible.module_utils._text import to_native
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
try:
from kubernetes.client.api import core_v1_api
from kubernetes.stream import stream
from kubernetes.stream.ws_client import (
ABNF,
ERROR_CHANNEL,
STDERR_CHANNEL,
STDOUT_CHANNEL,
)
except ImportError:
pass
try:
import yaml
except ImportError:
# ImportError are managed by the common module already.
pass
class K8SCopy(metaclass=ABCMeta):
def __init__(self, module, client):
self.client = client
self.module = module
self.api_instance = core_v1_api.CoreV1Api(client.client)
self.local_path = module.params.get("local_path")
self.name = module.params.get("pod")
self.namespace = module.params.get("namespace")
self.remote_path = module.params.get("remote_path")
self.content = module.params.get("content")
self.no_preserve = module.params.get("no_preserve")
self.container_arg = {}
if module.params.get("container"):
self.container_arg["container"] = module.params.get("container")
self.check_mode = self.module.check_mode
def _run_from_pod(self, cmd):
try:
resp = stream(
self.api_instance.connect_get_namespaced_pod_exec,
self.name,
self.namespace,
command=cmd,
async_req=False,
stderr=True,
stdin=False,
stdout=True,
tty=False,
_preload_content=False,
**self.container_arg,
)
stderr, stdout = [], []
while resp.is_open():
resp.update(timeout=1)
if resp.peek_stdout():
stdout.extend(resp.read_stdout().rstrip("\n").split("\n"))
if resp.peek_stderr():
stderr.extend(resp.read_stderr().rstrip("\n").split("\n"))
error = resp.read_channel(ERROR_CHANNEL)
resp.close()
error = yaml.safe_load(error)
return error, stdout, stderr
except Exception as e:
self.module.fail_json(
msg="Error while running/parsing from pod {1}/{2} command='{0}' : {3}".format(
self.namespace, self.name, cmd, to_native(e)
)
)
def is_directory_path_from_pod(self, file_path, failed_if_not_exists=True):
# check if file exists
error, out, err = self._run_from_pod(cmd=["test", "-e", file_path])
if error.get("status") != "Success":
if failed_if_not_exists:
return None, "%s does not exist in remote pod filesystem" % file_path
return False, None
error, out, err = self._run_from_pod(cmd=["test", "-d", file_path])
return error.get("status") == "Success", None
@abstractmethod
def run(self):
pass
class K8SCopyFromPod(K8SCopy):
"""
Copy files/directory from Pod into local filesystem
"""
def __init__(self, module, client):
super(K8SCopyFromPod, self).__init__(module, client)
self.is_remote_path_dir = None
self.files_to_copy = []
self._shellname = None
@property
def pod_shell(self):
if self._shellname is None:
for s in ("/bin/sh", "/bin/bash"):
error, out, err = self._run_from_pod(s)
if error.get("status") == "Success":
self._shellname = s
break
return self._shellname
def listfiles_with_find(self, path):
find_cmd = ["find", path, "-type", "f"]
error, files, err = self._run_from_pod(cmd=find_cmd)
if error.get("status") != "Success":
self.module.fail_json(msg=error.get("message"))
return files
def listfile_with_echo(self, path):
echo_cmd = [
self.pod_shell,
"-c",
"echo {path}/* {path}/.*".format(
path=path.translate(str.maketrans({" ": r"\ "}))
),
]
error, out, err = self._run_from_pod(cmd=echo_cmd)
if error.get("status") != "Success":
self.module.fail_json(msg=error.get("message"))
files = []
if out:
output = out[0] + " "
files = [
os.path.join(path, p[:-1])
for p in output.split(f"{path}/")
if p and p[:-1] not in (".", "..")
]
result = []
for f in files:
is_dir, err = self.is_directory_path_from_pod(f)
if err:
continue
if not is_dir:
result.append(f)
continue
result += self.listfile_with_echo(f)
return result
def list_remote_files(self):
"""
This method will check if the remote path is a dir or file
if it is a directory the file list will be updated accordingly
"""
# check is remote path exists and is a file or directory
is_dir, error = self.is_directory_path_from_pod(self.remote_path)
if error:
self.module.fail_json(msg=error)
if not is_dir:
return [self.remote_path]
else:
# find executable to list dir with
executables = dict(
find=self.listfiles_with_find,
echo=self.listfile_with_echo,
)
for item in executables:
error, out, err = self._run_from_pod(item)
if error.get("status") == "Success":
return executables.get(item)(self.remote_path)
def read(self):
self.stdout = None
self.stderr = None
if self.response.is_open():
if not self.response.sock.connected:
self.response._connected = False
else:
ret, out, err = select((self.response.sock.sock,), (), (), 0)
if ret:
code, frame = self.response.sock.recv_data_frame(True)
if code == ABNF.OPCODE_CLOSE:
self.response._connected = False
elif (
code in (ABNF.OPCODE_BINARY, ABNF.OPCODE_TEXT)
and len(frame.data) > 1
):
channel = frame.data[0]
content = frame.data[1:]
if content:
if channel == STDOUT_CHANNEL:
self.stdout = content
elif channel == STDERR_CHANNEL:
self.stderr = content.decode("utf-8", "replace")
def copy(self):
is_remote_path_dir = (
len(self.files_to_copy) > 1 or self.files_to_copy[0] != self.remote_path
)
relpath_start = self.remote_path
if is_remote_path_dir and os.path.isdir(self.local_path):
relpath_start = os.path.dirname(self.remote_path)
if not self.check_mode:
for remote_file in self.files_to_copy:
dest_file = self.local_path
if is_remote_path_dir:
dest_file = os.path.join(
self.local_path,
os.path.relpath(remote_file, start=relpath_start),
)
# create directory to copy file in
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
pod_command = ["cat", remote_file]
self.response = stream(
self.api_instance.connect_get_namespaced_pod_exec,
self.name,
self.namespace,
command=pod_command,
stderr=True,
stdin=True,
stdout=True,
tty=False,
_preload_content=False,
**self.container_arg,
)
errors = []
with open(dest_file, "wb") as fh:
while self.response._connected:
self.read()
if self.stdout:
fh.write(self.stdout)
if self.stderr:
errors.append(self.stderr)
if errors:
self.module.fail_json(
msg="Failed to copy file from Pod: {0}".format("".join(errors))
)
self.module.exit_json(
changed=True,
result="{0} successfully copied locally into {1}".format(
self.remote_path, self.local_path
),
)
def run(self):
self.files_to_copy = self.list_remote_files()
if self.files_to_copy == []:
self.module.exit_json(
changed=False,
warning="No file found from directory '{0}' into remote Pod.".format(
self.remote_path
),
)
self.copy()
class K8SCopyToPod(K8SCopy):
"""
Copy files/directory from local filesystem into remote Pod
"""
def __init__(self, module, client):
super(K8SCopyToPod, self).__init__(module, client)
self.files_to_copy = list()
def close_temp_file(self):
if self.named_temp_file:
self.named_temp_file.close()
def run(self):
# remove trailing slash from destination path
dest_file = self.remote_path.rstrip("/")
src_file = self.local_path
self.named_temp_file = None
if self.content:
self.named_temp_file = NamedTemporaryFile(mode="w")
self.named_temp_file.write(self.content)
self.named_temp_file.flush()
src_file = self.named_temp_file.name
else:
if not os.path.exists(self.local_path):
self.module.fail_json(
msg="{0} does not exist in local filesystem".format(self.local_path)
)
if not os.access(self.local_path, os.R_OK):
self.module.fail_json(msg="{0} not readable".format(self.local_path))
is_dir, err = self.is_directory_path_from_pod(
self.remote_path, failed_if_not_exists=False
)
if err:
self.module.fail_json(msg=err)
if is_dir:
if self.content:
self.module.fail_json(
msg="When content is specified, remote path should not be an existing directory"
)
else:
dest_file = os.path.join(dest_file, os.path.basename(src_file))
if not self.check_mode:
if self.no_preserve:
tar_command = [
"tar",
"--no-same-permissions",
"--no-same-owner",
"-xmf",
"-",
]
else:
tar_command = ["tar", "-xmf", "-"]
if dest_file.startswith("/"):
tar_command.extend(["-C", "/"])
response = stream(
self.api_instance.connect_get_namespaced_pod_exec,
self.name,
self.namespace,
command=tar_command,
stderr=True,
stdin=True,
stdout=True,
tty=False,
_preload_content=False,
**self.container_arg,
)
with TemporaryFile() as tar_buffer:
with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
tar.add(src_file, dest_file)
tar_buffer.seek(0)
commands = []
# push command in chunk mode
size = 1024 * 1024
while True:
data = tar_buffer.read(size)
if not data:
break
commands.append(data)
stderr, stdout = [], []
while response.is_open():
if response.peek_stdout():
stdout.append(response.read_stdout().rstrip("\n"))
if response.peek_stderr():
stderr.append(response.read_stderr().rstrip("\n"))
if commands:
cmd = commands.pop(0)
response.write_stdin(cmd)
else:
break
response.close()
if stderr:
self.close_temp_file()
self.module.fail_json(
command=tar_command,
msg="Failed to copy local file/directory into Pod due to: {0}".format(
"".join(stderr)
),
)
self.close_temp_file()
if self.content:
self.module.exit_json(
changed=True,
result="Content successfully copied into {0} on remote Pod".format(
self.remote_path
),
)
self.module.exit_json(
changed=True,
result="{0} successfully copied into remote Pod into {1}".format(
self.local_path, self.remote_path
),
)
def check_pod(svc):
module = svc.module
namespace = module.params.get("namespace")
name = module.params.get("pod")
container = module.params.get("container")
try:
resource = svc.find_resource("Pod", None, True)
except CoreException as e:
module.fail_json(msg=to_native(e))
def _fail(exc):
arg = {}
if hasattr(exc, "body"):
msg = (
"Namespace={0} Kind=Pod Name={1}: Failed requested object: {2}".format(
namespace, name, exc.body
)
)
else:
msg = to_native(exc)
for attr in ["status", "reason"]:
if hasattr(exc, attr):
arg[attr] = getattr(exc, attr)
module.fail_json(msg=msg, **arg)
try:
result = svc.client.get(resource, name=name, namespace=namespace)
containers = [
c["name"] for c in result.to_dict()["status"]["containerStatuses"]
]
if container and container not in containers:
module.fail_json(msg="Pod has no container {0}".format(container))
return containers
except Exception as exc:
_fail(exc)

View File

@@ -14,9 +14,8 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ApplyException(Exception):
"""Could not apply patch"""
""" Could not apply patch """

View File

@@ -15,16 +15,14 @@
# Implement ConfigMapHash and SecretHash equivalents
# 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
import hashlib
import json
import hashlib
try:
import string
maketrans = string.maketrans
except AttributeError:
maketrans = str.maketrans
@@ -37,7 +35,7 @@ except ImportError:
def sorted_dict(unsorted_dict):
result = OrderedDict()
for k, v in sorted(unsorted_dict.items()):
for (k, v) in sorted(unsorted_dict.items()):
if isinstance(v, dict):
v = sorted_dict(v)
result[k] = v
@@ -46,21 +44,14 @@ def sorted_dict(unsorted_dict):
def generate_hash(resource):
# Get name from metadata
metada = resource.get("metadata", {})
key = "name"
resource["name"] = metada.get("name", "")
generate_name = metada.get("generateName", "")
if resource["name"] == "" and generate_name:
del resource["name"]
key = "generateName"
resource["generateName"] = generate_name
if resource["kind"] == "ConfigMap":
marshalled = marshal(sorted_dict(resource), ["data", "kind", key])
del resource[key]
resource['name'] = resource.get('metadata', {}).get('name', '')
if resource['kind'] == 'ConfigMap':
marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name'])
del(resource['name'])
return encode(marshalled)
if resource["kind"] == "Secret":
marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"])
del resource[key]
if resource['kind'] == 'Secret':
marshalled = marshal(sorted_dict(resource), ['data', 'kind', 'name', 'type'])
del(resource['name'])
return encode(marshalled)
raise NotImplementedError
@@ -69,10 +60,8 @@ def marshal(data, keys):
ordered = OrderedDict()
for key in keys:
ordered[key] = data.get(key, "")
return json.dumps(ordered, separators=(",", ":")).encode("utf-8")
return json.dumps(ordered, separators=(',', ':')).encode('utf-8')
def encode(resource):
return (
hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
)
return hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))

View File

@@ -7,30 +7,138 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import copy
import json
from contextlib import contextmanager
import os
import re
import tempfile
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import string_types
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
from ansible.module_utils.basic import missing_required_lib
try:
import yaml
HAS_YAML = True
YAML_IMP_ERR = None
except ImportError:
YAML_IMP_ERR = traceback.format_exc()
HAS_YAML = False
def parse_helm_plugin_list(output=None):
@contextmanager
def prepare_helm_environ_update(module):
environ_update = {}
file_to_cleam_up = None
kubeconfig_path = module.params.get('kubeconfig')
if module.params.get('context') is not None:
environ_update["HELM_KUBECONTEXT"] = module.params.get('context')
if module.params.get('release_namespace'):
environ_update["HELM_NAMESPACE"] = module.params.get('release_namespace')
if module.params.get("api_key"):
environ_update["HELM_KUBETOKEN"] = module.params["api_key"]
if module.params.get("host"):
environ_update["HELM_KUBEAPISERVER"] = module.params["host"]
if module.params.get("validate_certs") is False or module.params.get("ca_cert"):
kubeconfig_path = write_temp_kubeconfig(
module.params["host"],
validate_certs=module.params["validate_certs"],
ca_cert=module.params["ca_cert"])
file_to_cleam_up = kubeconfig_path
if kubeconfig_path is not None:
environ_update["KUBECONFIG"] = kubeconfig_path
try:
yield environ_update
finally:
if file_to_cleam_up:
os.remove(file_to_cleam_up)
def run_helm(module, command, fails_on_error=True):
if not HAS_YAML:
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
with prepare_helm_environ_update(module) as environ_update:
rc, out, err = module.run_command(command, environ_update=environ_update)
if fails_on_error and rc != 0:
module.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
stdout=out,
stderr=err,
command=command,
)
return rc, out, err
def get_values(module, command, release_name):
"""
Get Values from deployed release
"""
if not HAS_YAML:
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
get_command = command + " get values --output=yaml " + release_name
rc, out, err = run_helm(module, get_command)
# Helm 3 return "null" string when no values are set
if out.rstrip("\n") == "null":
return {}
return yaml.safe_load(out)
def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None):
# Workaround until https://github.com/helm/helm/pull/8622 is merged
content = {
"apiVersion": "v1",
"kind": "Config",
"clusters": [
{
"cluster": {
"server": server,
},
"name": "generated-cluster"
}
],
"contexts": [
{
"context": {
"cluster": "generated-cluster"
},
"name": "generated-context"
}
],
"current-context": "generated-context"
}
if not validate_certs:
content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] = True
if ca_cert:
content["clusters"][0]["cluster"]["certificate-authority"] = ca_cert
_fd, file_name = tempfile.mkstemp()
with os.fdopen(_fd, 'w') as fp:
yaml.dump(content, fp)
return file_name
def get_helm_plugin_list(module, helm_bin=None):
"""
Return `helm plugin list`
"""
if not helm_bin:
return []
helm_plugin_list = helm_bin + " list"
rc, out, err = run_helm(module, helm_plugin_list)
if rc != 0 or (out == '' and err == ''):
module.fail_json(
msg="Failed to get Helm plugin info",
command=helm_plugin_list,
stdout=out,
stderr=err,
rc=rc,
)
return (rc, out, err)
def parse_helm_plugin_list(module, output=None):
"""
Parse `helm plugin list`, return list of plugins
"""
@@ -41,257 +149,12 @@ def parse_helm_plugin_list(output=None):
for line in output:
if line.startswith("NAME"):
continue
name, version, description = line.split("\t", 3)
name, version, description = line.split('\t', 3)
name = name.strip()
version = version.strip()
description = description.strip()
if name == "":
if name == '':
continue
ret.append((name, version, description))
return ret
def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None, kubeconfig=None):
# Workaround until https://github.com/helm/helm/pull/8622 is merged
content = {
"apiVersion": "v1",
"kind": "Config",
"clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}],
"contexts": [
{"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
],
"current-context": "generated-context",
}
if kubeconfig:
content = copy.deepcopy(kubeconfig)
for cluster in content["clusters"]:
if server:
cluster["cluster"]["server"] = server
if not validate_certs:
cluster["cluster"]["insecure-skip-tls-verify"] = True
if ca_cert:
cluster["cluster"]["certificate-authority"] = ca_cert
return content
class AnsibleHelmModule(object):
"""
An Ansible module class for Kubernetes.core helm modules
"""
def __init__(self, **kwargs):
self._module = None
if "module" in kwargs:
self._module = kwargs.get("module")
else:
self._module = AnsibleModule(**kwargs)
self.helm_env = None
def __getattr__(self, name):
return getattr(self._module, name)
@property
def params(self):
return self._module.params
def _prepare_helm_environment(self):
param_to_env_mapping = [
("context", "HELM_KUBECONTEXT"),
("release_namespace", "HELM_NAMESPACE"),
("api_key", "HELM_KUBETOKEN"),
("host", "HELM_KUBEAPISERVER"),
]
env_update = {}
for p, env in param_to_env_mapping:
if self.params.get(p):
env_update[env] = self.params.get(p)
kubeconfig_content = None
kubeconfig = self.params.get("kubeconfig")
if kubeconfig:
if isinstance(kubeconfig, string_types):
with open(os.path.expanduser(kubeconfig)) as fd:
kubeconfig_content = yaml.safe_load(fd)
elif isinstance(kubeconfig, dict):
kubeconfig_content = kubeconfig
if self.params.get("ca_cert"):
ca_cert = self.params.get("ca_cert")
if LooseVersion(self.get_helm_version()) < LooseVersion("3.5.0"):
# update certs from kubeconfig
kubeconfig_content = write_temp_kubeconfig(
server=self.params.get("host"),
ca_cert=ca_cert,
kubeconfig=kubeconfig_content,
)
else:
env_update["HELM_KUBECAFILE"] = ca_cert
if self.params.get("validate_certs") is False:
validate_certs = self.params.get("validate_certs")
if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
# update certs from kubeconfig
kubeconfig_content = write_temp_kubeconfig(
server=self.params.get("host"),
validate_certs=validate_certs,
kubeconfig=kubeconfig_content,
)
else:
env_update["HELM_KUBEINSECURE_SKIP_TLS_VERIFY"] = "true"
if kubeconfig_content:
fd, kubeconfig_path = tempfile.mkstemp()
with os.fdopen(fd, "w") as fp:
json.dump(kubeconfig_content, fp)
env_update["KUBECONFIG"] = kubeconfig_path
self.add_cleanup_file(kubeconfig_path)
return env_update
@property
def env_update(self):
if self.helm_env is None:
self.helm_env = self._prepare_helm_environment()
return self.helm_env
def run_helm_command(self, command, fails_on_error=True):
if not HAS_YAML:
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
rc, out, err = self.run_command(command, environ_update=self.env_update)
if fails_on_error and rc != 0:
self.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
rc, out, err
),
stdout=out,
stderr=err,
command=command,
)
return rc, out, err
def get_helm_binary(self):
return self.params.get("binary_path") or self.get_bin_path(
"helm", required=True
)
def get_helm_version(self):
command = self.get_helm_binary() + " version"
rc, out, err = self.run_command(command)
m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
if m:
return m.group(1)
m = re.match(r'Client: &version.Version{SemVer:"v([0-9\.]*)", ', out)
if m:
return m.group(1)
return None
def get_values(self, release_name, get_all=False):
"""
Get Values from deployed release
"""
if not HAS_YAML:
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
get_command = (
self.get_helm_binary() + " get values --output=yaml " + release_name
)
if get_all:
get_command += " -a"
rc, out, err = self.run_helm_command(get_command)
# Helm 3 return "null" string when no values are set
if out.rstrip("\n") == "null":
return {}
return yaml.safe_load(out)
def parse_yaml_content(self, content):
if not HAS_YAML:
self.fail_json(msg=missing_required_lib("yaml"), exception=HAS_YAML)
try:
return list(yaml.safe_load_all(content))
except (IOError, yaml.YAMLError) as exc:
self.fail_json(
msg="Error parsing YAML content: {0}".format(exc), raw_data=content
)
def get_manifest(self, release_name):
command = [
self.get_helm_binary(),
"get",
"manifest",
release_name,
]
rc, out, err = self.run_helm_command(" ".join(command))
if rc != 0:
self.fail_json(msg=err)
return self.parse_yaml_content(out)
def get_notes(self, release_name):
command = [
self.get_helm_binary(),
"get",
"notes",
release_name,
]
rc, out, err = self.run_helm_command(" ".join(command))
if rc != 0:
self.fail_json(msg=err)
return out
def get_hooks(self, release_name):
command = [
self.get_helm_binary(),
"get",
"hooks",
release_name,
]
rc, out, err = self.run_helm_command(" ".join(command))
if rc != 0:
self.fail_json(msg=err)
return self.parse_yaml_content(out)
def get_helm_plugin_list(self):
"""
Return `helm plugin list`
"""
helm_plugin_list = self.get_helm_binary() + " plugin list"
rc, out, err = self.run_helm_command(helm_plugin_list)
if rc != 0 or (out == "" and err == ""):
self.fail_json(
msg="Failed to get Helm plugin info",
command=helm_plugin_list,
stdout=out,
stderr=err,
rc=rc,
)
return (rc, out, err, helm_plugin_list)
def get_helm_set_values_args(self, set_values):
if any(v.get("value_type") == "json" for v in set_values):
if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
self.fail_json(
msg="This module requires helm >= 3.10.0, to use set_values parameter with value type set to 'json'. current version is {0}".format(
self.get_helm_version()
)
)
options = []
for opt in set_values:
value_type = opt.get("value_type", "raw")
value = opt.get("value")
if value_type == "raw":
options.append("--set " + value)
else:
options.append("--set-{0} '{1}'".format(value_type, value))
return " ".join(options)

View File

@@ -1,42 +0,0 @@
from __future__ import absolute_import, division, print_function
from ansible.module_utils.basic import env_fallback
__metaclass__ = type
HELM_AUTH_ARG_SPEC = dict(
binary_path=dict(type="path"),
context=dict(
type="str",
aliases=["kube_context"],
fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
),
kubeconfig=dict(
type="raw",
aliases=["kubeconfig_path"],
fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
),
host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
ca_cert=dict(
type="path",
aliases=["ssl_ca_cert"],
fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
),
validate_certs=dict(
type="bool",
default=True,
aliases=["verify_ssl"],
fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
),
api_key=dict(
type="str",
no_log=True,
fallback=(env_fallback, ["K8S_AUTH_API_KEY"]),
),
)
HELM_AUTH_MUTUALLY_EXCLUSIVE = [
("context", "ca_cert"),
("context", "validate_certs"),
]

View File

@@ -1,367 +0,0 @@
# Copyright: (c) 2021, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import hashlib
import os
from typing import Any, Dict, List, Optional
from ansible.module_utils.six import iteritems, string_types
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_MAP,
AUTH_ARG_SPEC,
AUTH_PROXY_HEADERS_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
requires as _requires,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
try:
from ansible_collections.kubernetes.core.plugins.module_utils import (
k8sdynamicclient,
)
from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import (
LazyDiscoverer,
)
except ImportError:
# Handled in module setup
pass
try:
import kubernetes
from kubernetes.dynamic.exceptions import (
ResourceNotFoundError,
ResourceNotUniqueError,
)
from kubernetes.dynamic.resource import Resource
except ImportError:
# kubernetes import error is handled in module setup
# This is defined only for the sake of Ansible's checked import requirement
Resource = Any # type: ignore
try:
import urllib3
urllib3.disable_warnings()
except ImportError:
# Handled in module setup
pass
_pool = {}
class unique_string(str):
_low = None
def __hash__(self):
return id(self)
def __eq__(self, other):
return self is other
def lower(self):
if self._low is None:
lower = str.lower(self)
if str.__eq__(lower, self):
self._low = self
else:
self._low = unique_string(lower)
return self._low
def _create_auth_spec(module=None, **kwargs) -> Dict:
auth: Dict = {}
# If authorization variables aren't defined, look for them in environment variables
for true_name, arg_name in AUTH_ARG_MAP.items():
if module and module.params.get(arg_name) is not None:
auth[true_name] = module.params.get(arg_name)
elif arg_name in kwargs and kwargs.get(arg_name) is not None:
auth[true_name] = kwargs.get(arg_name)
elif true_name in kwargs and kwargs.get(true_name) is not None:
# Aliases in kwargs
auth[true_name] = kwargs.get(true_name)
elif arg_name == "proxy_headers":
# specific case for 'proxy_headers' which is a dictionary
proxy_headers = {}
for key in AUTH_PROXY_HEADERS_SPEC.keys():
env_value = os.getenv(
"K8S_AUTH_PROXY_HEADERS_{0}".format(key.upper()), None
)
if env_value is not None:
if AUTH_PROXY_HEADERS_SPEC[key].get("type") == "bool":
env_value = env_value.lower() not in ["0", "false", "no"]
proxy_headers[key] = env_value
if proxy_headers is not {}:
auth[true_name] = proxy_headers
else:
env_value = os.getenv(
"K8S_AUTH_{0}".format(arg_name.upper()), None
) or os.getenv("K8S_AUTH_{0}".format(true_name.upper()), None)
if env_value is not None:
if AUTH_ARG_SPEC[arg_name].get("type") == "bool":
env_value = env_value.lower() not in ["0", "false", "no"]
auth[true_name] = env_value
return auth
def _load_config(auth: Dict) -> None:
kubeconfig = auth.get("kubeconfig")
optional_arg = {
"context": auth.get("context"),
"persist_config": auth.get("persist_config"),
}
if kubeconfig:
if isinstance(kubeconfig, string_types):
kubernetes.config.load_kube_config(config_file=kubeconfig, **optional_arg)
elif isinstance(kubeconfig, dict):
kubernetes.config.load_kube_config_from_dict(
config_dict=kubeconfig, **optional_arg
)
else:
kubernetes.config.load_kube_config(config_file=None, **optional_arg)
def _create_configuration(auth: Dict):
def auth_set(*names: list) -> bool:
return all(auth.get(name) for name in names)
if auth_set("host"):
# Removing trailing slashes if any from hostname
auth["host"] = auth.get("host").rstrip("/")
if (
auth_set("username", "password", "host")
or auth_set("api_key", "host")
or auth_set("cert_file", "key_file", "host")
):
# We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
pass
elif auth_set("kubeconfig") or auth_set("context"):
try:
_load_config(auth)
except Exception as err:
raise err
else:
# First try to do incluster config, then kubeconfig
try:
kubernetes.config.load_incluster_config()
except kubernetes.config.ConfigException:
try:
_load_config(auth)
except Exception as err:
raise err
# Override any values in the default configuration with Ansible parameters
# As of kubernetes-client v12.0.0, get_default_copy() is required here
try:
configuration = kubernetes.client.Configuration().get_default_copy()
except AttributeError:
configuration = kubernetes.client.Configuration()
for key, value in iteritems(auth):
if key in AUTH_ARG_MAP.keys() and value is not None:
if key == "api_key":
setattr(
configuration, key, {"authorization": "Bearer {0}".format(value)}
)
elif key == "proxy_headers":
headers = urllib3.util.make_headers(**value)
setattr(configuration, key, headers)
else:
setattr(configuration, key, value)
return configuration
def _create_headers(module=None, **kwargs):
header_map = {
"impersonate_user": "Impersonate-User",
"impersonate_groups": "Impersonate-Group",
}
headers = {}
for arg_name, header_name in header_map.items():
value = None
if module and module.params.get(arg_name) is not None:
value = module.params.get(arg_name)
elif arg_name in kwargs and kwargs.get(arg_name) is not None:
value = kwargs.get(arg_name)
else:
value = os.getenv("K8S_AUTH_{0}".format(arg_name.upper()), None)
if value is not None:
if AUTH_ARG_SPEC[arg_name].get("type") == "list":
value = [x for x in value.split(",") if x != ""]
if value:
headers[header_name] = value
return headers
def _configuration_digest(configuration, **kwargs) -> str:
m = hashlib.sha256()
for k in AUTH_ARG_MAP:
if not hasattr(configuration, k):
v = None
else:
v = getattr(configuration, k)
if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
with open(str(v), "r") as fd:
content = fd.read()
m.update(content.encode())
else:
m.update(str(v).encode())
for k, v in kwargs.items():
content = "{0}: {1}".format(k, v)
m.update(content.encode())
digest = m.hexdigest()
return digest
def _set_header(client, header, value):
if isinstance(value, list):
for v in value:
client.set_default_header(header_name=unique_string(header), header_value=v)
else:
client.set_default_header(header_name=header, header_value=value)
def cache(func):
def wrapper(*args, **kwargs):
client = None
hashable_kwargs = {}
for k, v in kwargs.items():
if isinstance(v, list):
hashable_kwargs[k] = ",".join(sorted(v))
else:
hashable_kwargs[k] = v
digest = _configuration_digest(*args, **hashable_kwargs)
if digest in _pool:
client = _pool[digest]
else:
client = func(*args, **kwargs)
_pool[digest] = client
return client
return wrapper
@cache
def create_api_client(configuration, **headers):
client = kubernetes.client.ApiClient(configuration)
for header, value in headers.items():
_set_header(client, header, value)
return k8sdynamicclient.K8SDynamicClient(client, discoverer=LazyDiscoverer)
class K8SClient:
"""A Client class for K8S modules.
This class has the primary purpose to proxy the kubernetes client and resource objects.
If there is a need for other methods or attributes to be proxied, they can be added here.
"""
K8S_SERVER_DRY_RUN = "All"
def __init__(self, configuration, client, dry_run: bool = False) -> None:
self.configuration = configuration
self.client = client
self.dry_run = dry_run
@property
def resources(self) -> List[Any]:
return self.client.resources
def _find_resource_with_prefix(
self, prefix: str, kind: str, api_version: str
) -> Resource:
for attribute in ["kind", "name", "singular_name"]:
try:
return self.client.resources.get(
**{"prefix": prefix, "api_version": api_version, attribute: kind}
)
except (ResourceNotFoundError, ResourceNotUniqueError):
pass
return self.client.resources.get(
prefix=prefix, api_version=api_version, short_names=[kind]
)
def resource(self, kind: str, api_version: str) -> Resource:
"""Fetch a kubernetes client resource.
This will attempt to find a kubernetes resource trying, in order, kind,
name, singular_name and short_names.
"""
try:
if api_version == "v1":
return self._find_resource_with_prefix("api", kind, api_version)
except ResourceNotFoundError:
pass
return self._find_resource_with_prefix(None, kind, api_version)
def _ensure_dry_run(self, params: Dict) -> Dict:
if self.dry_run:
params["dry_run"] = self.K8S_SERVER_DRY_RUN
return params
def validate(
self, resource, version: Optional[str] = None, strict: Optional[bool] = False
):
return self.client.validate(resource, version, strict)
def get(self, resource, **params):
return resource.get(**params)
def delete(self, resource, **params):
return resource.delete(**self._ensure_dry_run(params))
def apply(self, resource, definition, namespace, **params):
return resource.apply(
definition, namespace=namespace, **self._ensure_dry_run(params)
)
def create(self, resource, definition, **params):
return resource.create(definition, **self._ensure_dry_run(params))
def replace(self, resource, definition, **params):
return resource.replace(definition, **self._ensure_dry_run(params))
def patch(self, resource, definition, **params):
return resource.patch(definition, **self._ensure_dry_run(params))
def get_api_client(module=None, **kwargs: Optional[Any]) -> K8SClient:
auth_spec = _create_auth_spec(module, **kwargs)
if module:
requires = module.requires
else:
requires = _requires
if isinstance(auth_spec.get("kubeconfig"), dict):
requires("kubernetes", "17.17.0", "to use in-memory config")
if auth_spec.get("no_proxy"):
requires("kubernetes", "19.15.0", "to use the no_proxy feature")
try:
configuration = _create_configuration(auth_spec)
headers = _create_headers(module, **kwargs)
client = create_api_client(configuration, **headers)
except kubernetes.config.ConfigException as e:
msg = "Could not create API client: {0}".format(e)
raise CoreException(msg) from e
dry_run = False
if module and module.server_side_dry_run:
dry_run = True
k8s_client = K8SClient(
configuration=configuration,
client=client,
dry_run=dry_run,
)
return k8s_client

View File

@@ -1,172 +0,0 @@
import traceback
from typing import Optional
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_text
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
class AnsibleK8SModule:
"""A base module class for K8S modules.
This class should be used instead of directly using AnsibleModule. If there
is a need for other methods or attributes to be proxied, they can be added
here.
"""
default_settings = {
"check_k8s": True,
"check_pyyaml": True,
"module_class": AnsibleModule,
}
def __init__(self, **kwargs) -> None:
local_settings = {}
for key in AnsibleK8SModule.default_settings:
try:
local_settings[key] = kwargs.pop(key)
except KeyError:
local_settings[key] = AnsibleK8SModule.default_settings[key]
self.settings = local_settings
self._module = self.settings["module_class"](**kwargs)
if self.settings["check_k8s"]:
self.requires("kubernetes")
self.has_at_least("kubernetes", "24.2.0", warn=True)
if self.settings["check_pyyaml"]:
self.requires("pyyaml")
@property
def check_mode(self):
return self._module.check_mode
@property
def server_side_dry_run(self):
return self.check_mode and self.has_at_least("kubernetes", "18.20.0")
@property
def _diff(self):
return self._module._diff
@property
def _name(self):
return self._module._name
@property
def params(self):
return self._module.params
def warn(self, *args, **kwargs):
return self._module.warn(*args, **kwargs)
def deprecate(self, *args, **kwargs):
return self._module.deprecate(*args, **kwargs)
def debug(self, *args, **kwargs):
return self._module.debug(*args, **kwargs)
def exit_json(self, *args, **kwargs):
return self._module.exit_json(*args, **kwargs)
def fail_json(self, *args, **kwargs):
return self._module.fail_json(*args, **kwargs)
def fail_from_exception(self, exception):
msg = to_text(exception)
tb = "".join(
traceback.format_exception(None, exception, exception.__traceback__)
)
return self.fail_json(msg=msg, exception=tb)
def has_at_least(
self, dependency: str, minimum: Optional[str] = None, warn: bool = False
) -> bool:
supported = has_at_least(dependency, minimum)
if not supported and warn:
self.warn(
"{0}<{1} is not supported or tested. Some features may not work.".format(
dependency, minimum
)
)
return supported
def requires(
self,
dependency: str,
minimum: Optional[str] = None,
reason: Optional[str] = None,
) -> None:
try:
requires(dependency, minimum, reason=reason)
except Exception as e:
self.fail_json(msg=to_text(e))
def gather_versions() -> dict:
versions = {}
try:
import jsonpatch
versions["jsonpatch"] = jsonpatch.__version__
except ImportError:
pass
try:
import kubernetes
versions["kubernetes"] = kubernetes.__version__
except ImportError:
pass
try:
import kubernetes_validate
versions["kubernetes-validate"] = kubernetes_validate.__version__
except ImportError:
pass
try:
import yaml
versions["pyyaml"] = yaml.__version__
except ImportError:
pass
return versions
def has_at_least(dependency: str, minimum: Optional[str] = None) -> bool:
"""Check if a specific dependency is present at a minimum version.
If a minimum version is not specified it will check only that the
dependency is present.
"""
dependencies = gather_versions()
current = dependencies.get(dependency)
if current is not None:
if minimum is None:
return True
supported = LooseVersion(current) >= LooseVersion(minimum)
return supported
return False
def requires(
dependency: str, minimum: Optional[str] = None, reason: Optional[str] = None
) -> None:
"""Fail if a specific dependency is not present at a minimum version.
If a minimum version is not specified it will require only that the
dependency is present. This function raises an exception when the
dependency is not found at the required version.
"""
if not has_at_least(dependency, minimum):
if minimum is not None:
lib = "{0}>={1}".format(dependency, minimum)
else:
lib = dependency
raise Exception(missing_required_lib(lib, reason=reason))

View File

@@ -1,12 +0,0 @@
# Copyright: (c) 2021, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class CoreException(Exception):
pass
class ResourceTimeout(CoreException):
def __init__(self, message="", result=None):
self.result = result or {}
super().__init__(message)

View File

@@ -1,134 +0,0 @@
# Copyright: (c) 2021, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import os
from typing import Dict, Iterable, List, Optional, Union, cast
from ansible.module_utils.six import string_types
from ansible.module_utils.urls import Request
try:
import yaml
except ImportError:
# Handled in module setup
pass
class ResourceDefinition(dict):
"""Representation of a resource definition.
This is a thin wrapper around a dictionary representation of a resource
definition, with a few properties defined for conveniently accessing the
commonly used fields.
"""
@property
def kind(self) -> Optional[str]:
return self.get("kind")
@property
def api_version(self) -> Optional[str]:
return self.get("apiVersion")
@property
def namespace(self) -> Optional[str]:
metadata = self.get("metadata", {})
return metadata.get("namespace")
@property
def name(self) -> Optional[str]:
metadata = self.get("metadata", {})
return metadata.get("name")
def create_definitions(params: Dict) -> List[ResourceDefinition]:
"""Create a list of ResourceDefinitions from module inputs.
This will take the module's inputs and return a list of ResourceDefintion
objects. The resource definitions returned by this function should be as
complete a definition as we can create based on the input. Any *List kinds
will be removed and replaced by the resources contained in it.
"""
if params.get("resource_definition"):
d = cast(Union[str, List, Dict], params.get("resource_definition"))
definitions = from_yaml(d)
elif params.get("src"):
d = cast(str, params.get("src"))
if hasattr(d, "startswith") and d.startswith(("https://", "http://", "ftp://")):
data = Request().open("GET", d).read().decode("utf8")
definitions = from_yaml(data)
else:
definitions = from_file(d)
else:
# We'll create an empty definition and let merge_params set values
# from the module parameters.
definitions = [{}]
resource_definitions: List[Dict] = []
for definition in definitions:
merge_params(definition, params)
kind = cast(Optional[str], definition.get("kind"))
if kind and kind.endswith("List"):
resource_definitions += flatten_list_kind(definition, params)
else:
resource_definitions.append(definition)
return list(map(ResourceDefinition, resource_definitions))
def from_yaml(definition: Union[str, List, Dict]) -> Iterable[Dict]:
"""Load resource definitions from a yaml definition."""
definitions: List[Dict] = []
if isinstance(definition, string_types):
definitions += yaml.safe_load_all(definition)
elif isinstance(definition, list):
for item in definition:
if isinstance(item, string_types):
definitions += yaml.safe_load_all(item)
else:
definitions.append(item)
else:
definition = cast(Dict, definition)
definitions.append(definition)
return filter(None, definitions)
def from_file(filepath: str) -> Iterable[Dict]:
"""Load resource definitions from a path to a yaml file."""
path = os.path.normpath(filepath)
with open(path, "rb") as f:
definitions = list(yaml.safe_load_all(f))
return filter(None, definitions)
def merge_params(definition: Dict, params: Dict) -> Dict:
"""Merge module parameters with the resource definition.
Fields in the resource definition take precedence over module parameters.
"""
definition.setdefault("kind", params.get("kind"))
definition.setdefault("apiVersion", params.get("api_version"))
metadata = definition.setdefault("metadata", {})
# The following should only be set if we have values for them
if params.get("namespace"):
metadata.setdefault("namespace", params.get("namespace"))
if params.get("name"):
metadata.setdefault("name", params.get("name"))
if params.get("generate_name"):
metadata.setdefault("generateName", params.get("generate_name"))
return definition
def flatten_list_kind(definition: Dict, params: Dict) -> List[Dict]:
"""Replace *List kind with the items it contains.
This will take a definition for a *List resource and return a list of
definitions for the items contained within the List.
"""
items = []
kind = cast(str, definition.get("kind"))[:-4]
api_version = definition.get("apiVersion")
for item in definition.get("items", []):
item.setdefault("kind", kind)
item.setdefault("apiVersion", api_version)
items.append(merge_params(item, params))
return items

View File

@@ -1,237 +0,0 @@
# Copyright: (c) 2021, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from typing import Dict
from ansible.module_utils._text import to_native
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
ResourceTimeout,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
create_definitions,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
K8sService,
diff_objects,
hide_fields,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import exists
from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
LabelSelectorFilter,
)
def validate(client, module, resource):
def _prepend_resource_info(resource, msg):
return "%s %s: %s" % (resource["kind"], resource["metadata"]["name"], msg)
module.requires("kubernetes-validate")
warnings, errors = client.validate(
resource,
module.params["validate"].get("version"),
module.params["validate"].get("strict"),
)
if errors and module.params["validate"]["fail_on_error"]:
module.fail_json(
msg="\n".join([_prepend_resource_info(resource, error) for error in errors])
)
return [_prepend_resource_info(resource, msg) for msg in warnings + errors]
def get_definitions(svc, params):
try:
definitions = create_definitions(params)
except Exception as e:
msg = "Failed to load resource definition: {0}".format(e)
raise CoreException(msg) from e
delete_all = params.get("delete_all")
src = params.get("src")
resource_definition = params.get("resource_definition")
name = params.get("name")
state = params.get("state")
if (
delete_all
and state == "absent"
and name is None
and resource_definition is None
and src is None
):
# Delete all resources in the namespace for the specified resource type
if params.get("kind") is None:
raise CoreException(
"'kind' option is required to specify the resource type."
)
resource = svc.find_resource(
params.get("kind"), params.get("api_version"), fail=True
)
definitions = svc.retrieve_all(
resource,
params.get("namespace"),
params.get("label_selectors"),
)
return definitions
def run_module(module) -> None:
results = []
changed = False
client = get_api_client(module)
svc = K8sService(client, module)
definitions = get_definitions(svc, module.params)
for definition in definitions:
result = {"changed": False, "result": {}}
warnings = []
if module.params.get("validate") is not None:
warnings = validate(client, module, definition)
try:
result = perform_action(svc, definition, module.params)
except Exception as e:
try:
error = e.result
except AttributeError:
error = {}
try:
error["reason"] = e.__cause__.reason
except AttributeError:
pass
error["msg"] = to_native(e)
if warnings:
error.setdefault("warnings", []).extend(warnings)
if module.params.get("continue_on_error"):
result["error"] = error
else:
module.fail_json(**error)
if warnings:
result.setdefault("warnings", []).extend(warnings)
changed |= result["changed"]
results.append(result)
if len(results) == 1:
module.exit_json(**results[0])
module.exit_json(**{"changed": changed, "result": {"results": results}})
def perform_action(svc, definition: Dict, params: Dict) -> Dict:
origin_name = definition["metadata"].get("name")
namespace = definition["metadata"].get("namespace")
label_selectors = params.get("label_selectors")
state = params.get("state", None)
kind = definition.get("kind")
api_version = definition.get("apiVersion")
hidden_fields = params.get("hidden_fields")
result = {"changed": False, "result": {}}
instance = {}
warnings = []
resource = svc.find_resource(kind, api_version, fail=True)
definition["kind"] = resource.kind
definition["apiVersion"] = resource.group_version
existing = svc.retrieve(resource, definition)
if state == "absent":
if exists(existing) and existing.kind.endswith("List"):
instance = []
for item in existing.items:
r = svc.delete(resource, item, existing)
instance.append(r)
else:
instance = svc.delete(resource, definition, existing)
result["method"] = "delete"
if exists(existing):
result["changed"] = True
else:
if label_selectors:
filter_selector = LabelSelectorFilter(label_selectors)
if not filter_selector.isMatching(definition):
result["changed"] = False
result["msg"] = (
"resource 'kind={kind},name={name},namespace={namespace}' "
"filtered by label_selectors.".format(
kind=kind,
name=origin_name,
namespace=namespace,
)
)
return result
if params.get("apply"):
instance, warnings = svc.apply(resource, definition, existing)
result["method"] = "apply"
elif not existing:
if state == "patched":
result.setdefault("warnings", []).append(
"resource 'kind={kind},name={name}' was not found but will not be "
"created as 'state' parameter has been set to '{state}'".format(
kind=kind, name=definition["metadata"].get("name"), state=state
)
)
return result
instance, warnings = svc.create(resource, definition)
result["method"] = "create"
result["changed"] = True
elif params.get("force", False):
instance, warnings = svc.replace(resource, definition, existing)
result["method"] = "replace"
else:
instance, warnings = svc.update(resource, definition, existing)
result["method"] = "update"
if warnings:
result["warnings"] = warnings
# If needed, wait and/or create diff
success = True
if result["method"] == "delete":
# wait logic is a bit different for delete as `instance` may be a status object
if params.get("wait") and not svc.module.check_mode:
success, waited, duration = svc.wait(resource, definition)
result["duration"] = duration
else:
if params.get("wait") and not svc.module.check_mode:
success, instance, duration = svc.wait(resource, instance)
result["duration"] = duration
if result["method"] not in ("create", "delete"):
if existing:
existing = existing.to_dict()
else:
existing = {}
match, diffs = diff_objects(existing, instance, hidden_fields)
if match and diffs:
result.setdefault("warnings", []).append(
"No meaningful diff was generated, but the API may not be idempotent "
"(only metadata.generation or metadata.resourceVersion were changed)"
)
result["changed"] = not match
if svc.module._diff:
result["diff"] = diffs
result["result"] = hide_fields(instance, hidden_fields)
if not success:
raise ResourceTimeout(
'"{0}" "{1}": Timed out waiting on resource'.format(
definition["kind"], origin_name
),
result,
)
return result

View File

@@ -1,622 +0,0 @@
# Copyright: (c) 2021, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import copy
from json import loads
from re import compile
from typing import Any, Dict, List, Optional, Tuple
from ansible.module_utils.common.dict_transformations import dict_merge
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
generate_hash,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import requires
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
Waiter,
exists,
get_waiter,
resource_absent,
)
try:
from kubernetes.dynamic.exceptions import (
BadRequestError,
ConflictError,
ForbiddenError,
MethodNotAllowedError,
NotFoundError,
ResourceNotFoundError,
ResourceNotUniqueError,
)
except ImportError:
# Handled in module setup
pass
try:
from kubernetes.dynamic.resource import Resource, ResourceInstance
except ImportError:
# These are defined only for the sake of Ansible's checked import requirement
Resource = Any # type: ignore
ResourceInstance = Any # type: ignore
try:
from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
apply_object,
)
except ImportError:
# Handled in module setup
pass
try:
from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
recursive_diff,
)
except ImportError:
from ansible.module_utils.common.dict_transformations import recursive_diff
try:
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
_encode_stringdata,
)
except ImportError:
# Handled in module setup
pass
class K8sService:
"""A Service class for K8S modules.
This class has the primary purpose is to perform work on the cluster (e.g., create, apply, replace, update, delete).
"""
def __init__(self, client, module) -> None:
self.client = client
self.module = module
@property
def _client_side_dry_run(self):
return self.module.check_mode and not self.client.dry_run
def find_resource(
self, kind: str, api_version: str, fail: bool = False
) -> Optional[Resource]:
try:
return self.client.resource(kind, api_version)
except (ResourceNotFoundError, ResourceNotUniqueError):
if fail:
raise CoreException(
"Failed to find exact match for %s.%s by [kind, name, singularName, shortNames]"
% (api_version, kind)
)
def wait(
self, resource: Resource, instance: Dict
) -> Tuple[bool, Optional[Dict], int]:
wait_sleep = self.module.params.get("wait_sleep")
wait_timeout = self.module.params.get("wait_timeout")
wait_condition = None
if self.module.params.get("wait_condition") and self.module.params[
"wait_condition"
].get("type"):
wait_condition = self.module.params["wait_condition"]
state = "present"
if self.module.params.get("state") == "absent":
state = "absent"
label_selectors = self.module.params.get("label_selectors")
waiter = get_waiter(
self.client, resource, condition=wait_condition, state=state
)
return waiter.wait(
timeout=wait_timeout,
sleep=wait_sleep,
name=instance["metadata"].get("name"),
namespace=instance["metadata"].get("namespace"),
label_selectors=label_selectors,
)
def create_project_request(self, definition: Dict) -> Dict:
definition["kind"] = "ProjectRequest"
results = {"changed": False, "result": {}}
resource = self.find_resource(
"ProjectRequest", definition["apiVersion"], fail=True
)
if not self.module.check_mode:
try:
k8s_obj = self.client.create(resource, definition)
results["result"] = k8s_obj.to_dict()
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to create object: {0}".format(reason)
raise CoreException(msg) from e
results["changed"] = True
return results
def patch_resource(
self,
resource: Resource,
definition: Dict,
name: str,
namespace: str,
merge_type: str = None,
) -> Tuple[Dict, List[str]]:
try:
params = dict(name=name, namespace=namespace, serialize=False)
if merge_type:
params["content_type"] = "application/{0}-patch+json".format(merge_type)
return decode_response(self.client.patch(resource, definition, **params))
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to patch object: {0}".format(reason)
raise CoreException(msg) from e
def retrieve(self, resource: Resource, definition: Dict) -> ResourceInstance:
state = self.module.params.get("state", None)
append_hash = self.module.params.get("append_hash", False)
name = definition["metadata"].get("name")
generate_name = definition["metadata"].get("generateName")
namespace = definition["metadata"].get("namespace")
label_selectors = self.module.params.get("label_selectors")
existing: ResourceInstance = None
try:
# ignore append_hash for resources other than ConfigMap and Secret
if append_hash and definition["kind"] in ["ConfigMap", "Secret"]:
if name:
name = "%s-%s" % (name, generate_hash(definition))
definition["metadata"]["name"] = name
elif generate_name:
definition["metadata"]["generateName"] = "%s-%s" % (
generate_name,
generate_hash(definition),
)
params = {}
if name:
params["name"] = name
if namespace:
params["namespace"] = namespace
if label_selectors:
params["label_selector"] = ",".join(label_selectors)
if "name" in params or "label_selector" in params:
existing = self.client.get(resource, **params)
except (NotFoundError, MethodNotAllowedError):
pass
except ForbiddenError as e:
if (
definition["kind"] in ["Project", "ProjectRequest"]
and state != "absent"
):
return self.create_project_request(definition)
reason = e.body if hasattr(e, "body") else e
msg = "Failed to retrieve requested object: {0}".format(reason)
raise CoreException(msg) from e
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to retrieve requested object: {0}".format(reason)
raise CoreException(msg) from e
return existing
def retrieve_all(
self, resource: Resource, namespace: str, label_selectors: List[str] = None
) -> List[Dict]:
definitions: List[ResourceInstance] = []
try:
params = dict(namespace=namespace)
if label_selectors:
params["label_selector"] = ",".join(label_selectors)
resource_list = self.client.get(resource, **params)
for item in resource_list.items:
existing = self.client.get(
resource, name=item.metadata.name, namespace=namespace
)
definitions.append(existing.to_dict())
except (NotFoundError, MethodNotAllowedError):
pass
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to retrieve requested object: {0}".format(reason)
raise CoreException(msg) from e
return definitions
def find(
self,
kind: str,
api_version: str,
name: str = None,
namespace: Optional[str] = None,
label_selectors: Optional[List[str]] = None,
field_selectors: Optional[List[str]] = None,
wait: Optional[bool] = False,
wait_sleep: Optional[int] = 5,
wait_timeout: Optional[int] = 120,
state: Optional[str] = "present",
condition: Optional[Dict] = None,
hidden_fields: Optional[List] = None,
) -> Dict:
resource = self.find_resource(kind, api_version)
api_found = bool(resource)
if not api_found:
return dict(
resources=[],
msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(
api_version, kind
),
api_found=False,
)
if not label_selectors:
label_selectors = []
if not field_selectors:
field_selectors = []
result = {"resources": [], "api_found": True}
# With a timeout of 0 the waiter will do a single check and return, effectively not waiting.
if not wait:
wait_timeout = 0
if state == "present":
predicate = exists
else:
predicate = resource_absent
waiter = Waiter(self.client, resource, predicate)
# This is an initial check to get the resource or resources that we then need to wait on individually.
try:
success, resources, duration = waiter.wait(
timeout=wait_timeout,
sleep=wait_sleep,
name=name,
namespace=namespace,
label_selectors=label_selectors,
field_selectors=field_selectors,
)
except BadRequestError:
return result
except CoreException as e:
raise e
except Exception as e:
raise CoreException(
"Exception '{0}' raised while trying to get resource using (name={1}, namespace={2}, label_selectors={3}, field_selectors={4})".format(
e, name, namespace, label_selectors, field_selectors
)
)
# There is either no result or there is a List resource with no items
if (
not resources
or resources["kind"].endswith("List")
and not resources.get("items")
):
return result
instances = resources.get("items") or [resources]
if not wait:
result["resources"] = [
hide_fields(instance, hidden_fields) for instance in instances
]
return result
# Now wait for the specified state of any resource instances we have found.
waiter = get_waiter(self.client, resource, state=state, condition=condition)
for instance in instances:
name = instance["metadata"].get("name")
namespace = instance["metadata"].get("namespace")
success, res, duration = waiter.wait(
timeout=wait_timeout,
sleep=wait_sleep,
name=name,
namespace=namespace,
)
if not success:
raise CoreException(
"Failed to gather information about %s(s) even"
" after waiting for %s seconds" % (res.get("kind"), duration)
)
result["resources"].append(hide_fields(res, hidden_fields))
return result
def create(self, resource: Resource, definition: Dict) -> Tuple[Dict, List[str]]:
namespace = definition["metadata"].get("namespace")
name = definition["metadata"].get("name")
if self._client_side_dry_run:
return _encode_stringdata(definition), []
try:
return decode_response(
self.client.create(
resource, definition, namespace=namespace, serialize=False
)
)
except ConflictError:
# Some resources, like ProjectRequests, can't be created multiple times,
# because the resources that they create don't match their kind
# In this case we'll mark it as unchanged and warn the user
self.module.warn(
"{0} was not found, but creating it returned a 409 Conflict error. This can happen \
if the resource you are creating does not directly create a resource of the same kind.".format(
name
)
)
return dict(), []
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to create object: {0}".format(reason)
raise CoreException(msg) from e
def apply(
self,
resource: Resource,
definition: Dict,
existing: Optional[ResourceInstance] = None,
) -> Tuple[Dict, List[str]]:
namespace = definition["metadata"].get("namespace")
server_side_apply = self.module.params.get("server_side_apply")
if server_side_apply:
requires("kubernetes", "19.15.0", reason="to use server side apply")
if self._client_side_dry_run:
ignored, patch = apply_object(resource, _encode_stringdata(definition))
if existing:
return dict_merge(existing.to_dict(), patch), []
else:
return patch, []
try:
params = {}
if server_side_apply:
params["server_side"] = True
params.update(server_side_apply)
return decode_response(
self.client.apply(
resource, definition, namespace=namespace, serialize=False, **params
)
)
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to apply object: {0}".format(reason)
raise CoreException(msg) from e
def replace(
self,
resource: Resource,
definition: Dict,
existing: ResourceInstance,
) -> Tuple[Dict, List[str]]:
append_hash = self.module.params.get("append_hash", False)
name = definition["metadata"].get("name")
namespace = definition["metadata"].get("namespace")
if self._client_side_dry_run:
return _encode_stringdata(definition), []
try:
return decode_response(
self.client.replace(
resource,
definition,
name=name,
namespace=namespace,
append_hash=append_hash,
serialize=False,
)
)
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to replace object: {0}".format(reason)
raise CoreException(msg) from e
def update(
self, resource: Resource, definition: Dict, existing: ResourceInstance
) -> Tuple[Dict, List[str]]:
name = definition["metadata"].get("name")
namespace = definition["metadata"].get("namespace")
if self._client_side_dry_run:
return dict_merge(existing.to_dict(), _encode_stringdata(definition)), []
exception = None
for merge_type in self.module.params.get("merge_type") or [
"strategic-merge",
"merge",
]:
try:
return self.patch_resource(
resource,
definition,
name,
namespace,
merge_type=merge_type,
)
except CoreException as e:
exception = e
continue
raise exception
def delete(
self,
resource: Resource,
definition: Dict,
existing: Optional[ResourceInstance] = None,
) -> Dict:
delete_options = self.module.params.get("delete_options")
label_selectors = self.module.params.get("label_selectors")
name = definition["metadata"].get("name")
namespace = definition["metadata"].get("namespace")
params = {}
if not exists(existing):
return {}
# Delete the object
if self._client_side_dry_run:
return {}
if name:
params["name"] = name
if namespace:
params["namespace"] = namespace
if label_selectors:
params["label_selector"] = ",".join(label_selectors)
if delete_options:
body = {
"apiVersion": "v1",
"kind": "DeleteOptions",
}
body.update(delete_options)
params["body"] = body
try:
k8s_obj = self.client.delete(resource, **params).to_dict()
except Exception as e:
reason = e.body if hasattr(e, "body") else e
msg = "Failed to delete object: {0}".format(reason)
raise CoreException(msg) from e
return k8s_obj
def diff_objects(
existing: Dict, new: Dict, hidden_fields: Optional[list] = None
) -> Tuple[bool, Dict]:
result = {}
diff = recursive_diff(existing, new)
if not diff:
return True, result
result["before"] = diff[0]
result["after"] = diff[1]
if list(result["after"].keys()) != ["metadata"] or list(
result["before"].keys()
) != ["metadata"]:
return False, result
# If only metadata.generation and metadata.resourceVersion changed, ignore it
ignored_keys = set(["generation", "resourceVersion"])
if not set(result["after"]["metadata"].keys()).issubset(ignored_keys):
return False, result
if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
return False, result
result["before"] = hide_fields(result["before"], hidden_fields)
result["after"] = hide_fields(result["after"], hidden_fields)
return True, result
def hide_fields(definition: dict, hidden_fields: Optional[list]) -> dict:
if not hidden_fields:
return definition
result = copy.deepcopy(definition)
for hidden_field in hidden_fields:
result = hide_field(result, hidden_field)
return result
# hide_field is not hugely sophisticated and designed to cope
# with e.g. status or metadata.managedFields rather than e.g.
# spec.template.spec.containers[0].env[3].value
def hide_field(definition: dict, hidden_field: str) -> dict:
split = hidden_field.split(".", 1)
if split[0] in definition:
if len(split) == 2:
definition[split[0]] = hide_field(definition[split[0]], split[1])
else:
del definition[split[0]]
return definition
def decode_response(resp) -> Tuple[Dict, List[str]]:
"""
This function decodes unserialized responses from the Kubernetes python
client and decodes the RFC2616 14.46 warnings found in the response
headers.
"""
obj = ResourceInstance(None, loads(resp.data.decode("utf8"))).to_dict()
warnings = []
if (
resp.headers is not None
and "warning" in resp.headers
and resp.headers["warning"] is not None
):
warnings = resp.headers["warning"].split(", ")
return obj, decode_warnings(warnings)
def decode_warnings(warnings: str) -> List[str]:
"""
This function decodes RFC2616 14.46 warnings in a simplified way, where
only the warn-texts are returned in a list.
"""
p = compile('\\d{3} .+ (".+")')
decoded = []
for warning in warnings:
m = p.match(warning)
if m:
try:
parsed, unused = parse_quoted_string(m.group(1))
decoded.append(parsed)
except ValueError:
continue
return decoded
def parse_quoted_string(quoted_string: str) -> Tuple[str, str]:
"""
This function was adapted from:
https://github.com/kubernetes/apimachinery/blob/bb8822152cabfb4f34dbc26270f874ce53db50de/pkg/util/net/http.go#L609
"""
if len(quoted_string) == 0:
raise ValueError("invalid quoted string: 0-length")
if quoted_string[0] != '"':
raise ValueError("invalid quoted string: missing initial quote")
quoted_string = quoted_string[1:]
remainder = ""
escaping = False
closed_quote = False
result = []
for i, b in enumerate(quoted_string):
if b == '"':
if escaping:
result.append(b)
escaping = False
else:
closed_quote = True
remainder_start = i + 1
remainder = quoted_string[remainder_start:].strip()
break
elif b == "\\":
if escaping:
result.append(b)
escaping = False
else:
escaping = True
else:
result.append(b)
escaping = False
if not closed_quote:
raise ValueError("invalid quoted string: missing closing quote")
return "".join(result), remainder

View File

@@ -1,244 +0,0 @@
import time
from functools import partial
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
from ansible.module_utils.parsing.convert_bool import boolean
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
try:
from kubernetes.dynamic.exceptions import NotFoundError
from kubernetes.dynamic.resource import Resource, ResourceField, ResourceInstance
except ImportError:
# These are defined only for the sake of Ansible's checked import requirement
Resource = Any # type: ignore
ResourceInstance = Any # type: ignore
pass
try:
from urllib3.exceptions import HTTPError
except ImportError:
# Handled during module setup
pass
def deployment_ready(deployment: ResourceInstance) -> bool:
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
# deployment.status.replicas is None is perfectly ok if desired replicas == 0
# Scaling up means that we also need to check that we're not in a
# situation where status.replicas == status.availableReplicas
# but spec.replicas != status.replicas
return bool(
deployment.status
and deployment.spec.replicas == (deployment.status.replicas or 0)
and deployment.status.availableReplicas == deployment.status.replicas
and deployment.status.observedGeneration == deployment.metadata.generation
and not deployment.status.unavailableReplicas
)
def pod_ready(pod: ResourceInstance) -> bool:
return bool(
pod.status
and pod.status.containerStatuses is not None
and all(container.ready for container in pod.status.containerStatuses)
)
def daemonset_ready(daemonset: ResourceInstance) -> bool:
return bool(
daemonset.status
and daemonset.status.desiredNumberScheduled is not None
and daemonset.status.updatedNumberScheduled
== daemonset.status.desiredNumberScheduled
and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled
and daemonset.status.observedGeneration == daemonset.metadata.generation
and not daemonset.status.unavailableReplicas
)
def statefulset_ready(statefulset: ResourceInstance) -> bool:
if statefulset.spec.updateStrategy.type == "OnDelete":
return bool(
statefulset.status
and statefulset.status.observedGeneration
== (statefulset.metadata.generation or 0)
and statefulset.status.replicas == statefulset.spec.replicas
)
# These may be None
updated_replicas = statefulset.status.updatedReplicas or 0
ready_replicas = statefulset.status.readyReplicas or 0
return bool(
statefulset.status
and statefulset.spec.updateStrategy.type == "RollingUpdate"
and statefulset.status.observedGeneration
== (statefulset.metadata.generation or 0)
and statefulset.status.updateRevision == statefulset.status.currentRevision
and updated_replicas == statefulset.spec.replicas
and ready_replicas == statefulset.spec.replicas
and statefulset.status.replicas == statefulset.spec.replicas
)
def custom_condition(condition: Dict, resource: ResourceInstance) -> bool:
if not resource.status or not resource.status.conditions:
return False
matches = [x for x in resource.status.conditions if x.type == condition["type"]]
if not matches:
return False
# There should never be more than one condition of a specific type
match: ResourceField = matches[0]
if match.status == "Unknown":
if match.status == condition["status"]:
if "reason" not in condition:
return True
if condition["reason"]:
return match.reason == condition["reason"]
return False
status = True if match.status == "True" else False
if status == boolean(condition["status"], strict=False):
if condition.get("reason"):
return match.reason == condition["reason"]
return True
return False
def resource_absent(resource: ResourceInstance) -> bool:
return not exists(resource)
def exists(resource: Optional[ResourceInstance]) -> bool:
"""Simple predicate to check for existence of a resource.
While a List type resource technically always exists, this will only return
true if the List contains items."""
return bool(resource) and not empty_list(resource)
RESOURCE_PREDICATES = {
"DaemonSet": daemonset_ready,
"Deployment": deployment_ready,
"Pod": pod_ready,
"StatefulSet": statefulset_ready,
}
def empty_list(resource: ResourceInstance) -> bool:
return resource["kind"].endswith("List") and not resource.get("items")
def clock(total: int, interval: int) -> Iterator[int]:
start = time.monotonic()
yield 0
while (time.monotonic() - start) < total:
time.sleep(interval)
yield int(time.monotonic() - start)
class Waiter:
def __init__(
self, client, resource: Resource, predicate: Callable[[ResourceInstance], bool]
):
self.client = client
self.resource = resource
self.predicate = predicate
def wait(
self,
timeout: int,
sleep: int,
name: Optional[str] = None,
namespace: Optional[str] = None,
label_selectors: Optional[List[str]] = None,
field_selectors: Optional[List[str]] = None,
) -> Tuple[bool, Dict, int]:
params = {}
if name:
params["name"] = name
if namespace:
params["namespace"] = namespace
if label_selectors:
params["label_selector"] = ",".join(label_selectors)
if field_selectors:
params["field_selector"] = ",".join(field_selectors)
instance = {}
response = None
elapsed = 0
for i in clock(timeout, sleep):
exception = None
elapsed = i
try:
response = self.client.get(self.resource, **params)
except NotFoundError:
response = None
# Retry connection errors as it may be intermittent network issues
except HTTPError as e:
exception = e
if self.predicate(response):
break
if exception:
msg = (
"Exception '{0}' raised while trying to get resource using {1}".format(
exception, params
)
)
raise CoreException(msg) from exception
if response:
instance = response.to_dict()
return self.predicate(response), instance, elapsed
class DummyWaiter:
"""A no-op waiter that simply returns the item being waited on.
No API call will be made with this waiter; the function returns
immediately. This waiter is useful for waiting on resource instances in
check mode, for example.
"""
def wait(
self,
definition: Dict,
timeout: int,
sleep: int,
label_selectors: Optional[List[str]] = None,
) -> Tuple[bool, Optional[Dict], int]:
return True, definition, 0
# The better solution would be typing.Protocol, but this is only in 3.8+
SupportsWait = Union[Waiter, DummyWaiter]
def get_waiter(
client,
resource: Resource,
state: str = "present",
condition: Optional[Dict] = None,
check_mode: Optional[bool] = False,
) -> SupportsWait:
"""Create a Waiter object based on the specified resource.
This is a convenience method for creating a waiter from a resource.
Based on the arguments and the kind of resource, an appropriate waiter
will be returned. A waiter can also be created directly, of course.
"""
if check_mode:
return DummyWaiter()
if state == "present":
if condition:
predicate: Callable[[ResourceInstance], bool] = partial(
custom_condition, condition
)
else:
predicate = RESOURCE_PREDICATES.get(resource.kind, exists)
else:
predicate = resource_absent
return Waiter(client, resource, predicate)

View File

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

View File

@@ -1,78 +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,17 +4,16 @@
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
---
module: helm
short_description: Manages Kubernetes packages with the Helm package manager
version_added: 0.11.0
version_added: "0.11.0"
author:
- Lucas Boisserie (@LucasBoisserie)
@@ -27,10 +26,6 @@ requirements:
description:
- 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:
chart_ref:
description:
@@ -51,17 +46,6 @@ options:
- Chart version to install. If this is not specified, the latest version is installed.
required: false
type: str
dependency_update:
description:
- Run standalone C(helm dependency update CHART) before the operation.
- Run inline C(--dependency-update) with C(helm install) command. This feature is not supported yet with the C(helm upgrade) command.
- So we should consider to use I(dependency_update) options with I(replace) option enabled when specifying I(chart_repo_url).
- The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
- For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
default: false
type: bool
aliases: [ dep_up ]
version_added: 2.4.0
release_name:
description:
- Release name to manage.
@@ -99,52 +83,12 @@ options:
default: []
type: list
elements: str
version_added: 1.1.0
version_added: '1.1.0'
update_repo_cache:
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
type: bool
set_values:
description:
- Values to pass to chart configuration
required: false
type: list
elements: dict
suboptions:
value:
description:
- Value to pass to chart configuration (e.g phase=prod).
type: str
required: true
value_type:
description:
- Use C(raw) set individual value.
- Use C(string) to force a string for an individual value.
- Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
- Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
default: raw
choices:
- raw
- string
- json
- file
version_added: 2.4.0
reuse_values:
description:
- When upgrading package, specifies wether to reuse the last release's values and merge in any overrides from parameters I(release_values),
I(values_files) or I(set_values).
- If I(reset_values) is set to C(True), this is ignored.
type: bool
required: false
version_added: 2.5.0
reset_values:
description:
- When upgrading package, reset the values to the ones built into the chart.
type: bool
required: false
default: True
version_added: 2.5.0
#Helm options
disable_hook:
@@ -164,24 +108,13 @@ options:
type: bool
wait:
description:
- When I(release_state) is set to C(present), wait until all Pods, PVCs, Services,
and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
- When I(release_state) is set to C(absent), will wait until all the resources are deleted before returning.
It will wait for as long as I(wait_timeout). This feature requires helm>=3.7.0. Added in version 2.3.0.
- 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.
default: False
type: bool
wait_timeout:
description:
- 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
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:
description:
- If set, the installation process deletes the installation on failure.
@@ -192,37 +125,25 @@ options:
- Create the release namespace if not present.
type: bool
default: False
version_added: 0.11.1
post_renderer:
description:
- Path to an executable to be used for post rendering.
type: str
version_added: 2.4.0
version_added: "0.11.1"
replace:
description:
- Reuse the given name, only if that name is a deleted release which remains in the history.
- This is unsafe in production environment.
- mutually exclusive with with C(history_max).
type: bool
default: False
version_added: 1.11.0
version_added: "1.11.0"
skip_crds:
description:
- Skip custom resource definitions when installing or upgrading.
type: bool
default: False
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
version_added: "1.2.0"
extends_documentation_fragment:
- kubernetes.core.helm_common_options
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
kubernetes.core.helm:
name: test
@@ -265,22 +186,6 @@ EXAMPLES = r"""
state: absent
wait: true
- name: Separately update the repository cache
kubernetes.core.helm:
name: dummy
namespace: kube-system
state: absent
update_repo_cache: true
- name: Deploy Grafana chart using set values on target
kubernetes.core.helm:
name: test
chart_ref: stable/grafana
release_namespace: monitoring
set_values:
- value: phase=prod
value_type: string
# From git
- name: Git clone stable repo on HEAD
ansible.builtin.git:
@@ -325,18 +230,7 @@ EXAMPLES = r"""
enabled: True
logging:
enabled: True
# Deploy latest version
- name: Deploy latest version of Grafana chart using reuse_values
kubernetes.core.helm:
name: test
chart_ref: stable/grafana
release_namespace: monitoring
reuse_values: true
values:
replicas: 2
version: 3e8ec0b2dffa40fb97d5342e4af887de95faa8c61a62480dd7f8aa03dffcf533
"""
'''
RETURN = r"""
status:
@@ -393,31 +287,22 @@ command:
sample: helm upgrade ...
"""
import copy
import re
import tempfile
import traceback
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
try:
import yaml
IMP_YAML = True
IMP_YAML_ERR = None
except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
parse_helm_plugin_list,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
run_helm,
get_values,
get_helm_plugin_list,
parse_helm_plugin_list
)
@@ -428,103 +313,63 @@ def get_release(state, release_name):
if state is not None:
for release in state:
if release["name"] == release_name:
if release['name'] == release_name:
return release
return None
def get_release_status(module, release_name, all_status=False):
def get_release_status(module, command, release_name):
"""
Get Release state from all release status (deployed, failed, pending-install, etc)
Get Release state from deployed release
"""
list_command = [
module.get_helm_binary(),
"list",
"--output=yaml",
"--filter",
release_name,
]
if all_status:
list_command.append("--all")
list_command = command + " list --output=yaml --filter " + release_name
rc, out, err = module.run_helm_command(list_command)
rc, out, err = run_helm(module, list_command)
release = get_release(yaml.safe_load(out), release_name)
if release is None: # not install
return None
release["values"] = module.get_values(release_name)
release['values'] = get_values(module, command, release_name)
return release
def run_repo_update(module):
def run_repo_update(module, command):
"""
Run Repo update
"""
repo_update_command = module.get_helm_binary() + " repo update"
rc, out, err = module.run_helm_command(repo_update_command)
def run_dep_update(module, chart_ref):
"""
Run dependency update
"""
dep_update = module.get_helm_binary() + f" dependency update '{chart_ref}'"
rc, out, err = module.run_helm_command(dep_update)
repo_update_command = command + " repo update"
rc, out, err = run_helm(module, repo_update_command)
def fetch_chart_info(module, command, chart_ref):
"""
Get chart info
"""
inspect_command = command + f" show chart '{chart_ref}'"
inspect_command = command + " show chart " + chart_ref
rc, out, err = module.run_helm_command(inspect_command)
rc, out, err = run_helm(module, inspect_command)
return yaml.safe_load(out)
def deploy(
module,
command,
release_name,
release_values,
chart_name,
wait,
wait_timeout,
disable_hook,
force,
values_files,
history_max,
atomic=False,
create_namespace=False,
replace=False,
post_renderer=None,
skip_crds=False,
timeout=None,
dependency_update=None,
set_value_args=None,
reuse_values=None,
reset_values=True,
):
def deploy(command, release_name, release_values, chart_name, wait,
wait_timeout, disable_hook, force, values_files, atomic=False,
create_namespace=False, replace=False, skip_crds=False):
"""
Install/upgrade/rollback release chart
"""
if replace:
# '--replace' is not supported by 'upgrade -i'
deploy_command = command + " install"
if dependency_update:
deploy_command += " --dependency-update"
else:
deploy_command = command + " upgrade -i" # install/upgrade
if reset_values:
deploy_command += " --reset-values"
if reuse_values is not None:
deploy_command += " --reuse-values=" + str(reuse_values)
# Always reset values to keep release_values equal to values released
deploy_command += " --reset-values"
if wait:
deploy_command += " --wait"
@@ -534,9 +379,6 @@ def deploy(
if atomic:
deploy_command += " --atomic"
if timeout:
deploy_command += " --timeout " + timeout
if force:
deploy_command += " --force"
@@ -554,29 +396,20 @@ def deploy(
deploy_command += " --values=" + value_file
if release_values != {}:
fd, path = tempfile.mkstemp(suffix=".yml")
with open(path, "w") as yaml_file:
fd, path = tempfile.mkstemp(suffix='.yml')
with open(path, 'w') as yaml_file:
yaml.dump(release_values, yaml_file, default_flow_style=False)
deploy_command += " -f=" + path
module.add_cleanup_file(path)
if post_renderer:
deploy_command += " --post-renderer=" + post_renderer
if 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
if set_value_args:
deploy_command += " " + set_value_args
deploy_command += " " + release_name + f" '{chart_name}'"
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
"""
@@ -589,12 +422,6 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
if disable_hook:
delete_command += " --no-hooks"
if wait:
delete_command += " --wait"
if wait_timeout is not None:
delete_command += " --timeout " + wait_timeout
delete_command += " " + release_name
return delete_command
@@ -603,7 +430,7 @@ def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
def load_values_files(values_files):
values = {}
for values_file in values_files or []:
with open(values_file, "r") as fd:
with open(values_file, 'r') as fd:
content = yaml.safe_load(fd)
if not isinstance(content, dict):
continue
@@ -612,72 +439,50 @@ def load_values_files(values_files):
return values
def get_plugin_version(plugin):
def has_plugin(command, plugin):
"""
Check if helm plugin is installed and return corresponding version
Check if helm plugin is installed.
"""
rc, output, err, command = module.get_helm_plugin_list()
out = parse_helm_plugin_list(output=output.splitlines())
cmd = command + " plugin"
rc, output, err = get_helm_plugin_list(module, helm_bin=cmd)
out = parse_helm_plugin_list(module, output=output.splitlines())
if not out:
return None
return False
for line in out:
if line[0] == plugin:
return line[1]
return None
return True
return False
def helmdiff_check(
module,
release_name,
chart_ref,
release_values,
values_files=None,
chart_version=None,
replace=False,
chart_repo_url=None,
post_renderer=False,
set_value_args=None,
reuse_values=None,
reset_values=True,
):
def helmdiff_check(module, helm_cmd, release_name, chart_ref, release_values,
values_files=None, chart_version=None, replace=False):
"""
Use helm diff to determine if a release would change by upgrading a chart.
"""
cmd = module.get_helm_binary() + " diff upgrade"
cmd = helm_cmd + " diff upgrade"
cmd += " " + release_name
cmd += " " + chart_ref
if chart_repo_url is not None:
cmd += " " + "--repo=" + chart_repo_url
if chart_version is not None:
cmd += " " + "--version=" + chart_version
if not replace:
cmd += " " + "--reset-values=" + str(reset_values)
if post_renderer:
cmd += " --post-renderer=" + post_renderer
if values_files:
for value_file in values_files:
cmd += " --values=" + value_file
cmd += " " + "--reset-values"
if release_values != {}:
fd, path = tempfile.mkstemp(suffix=".yml")
with open(path, "w") as yaml_file:
fd, path = tempfile.mkstemp(suffix='.yml')
with open(path, 'w') as yaml_file:
yaml.dump(release_values, yaml_file, default_flow_style=False)
cmd += " -f=" + path
module.add_cleanup_file(path)
if set_value_args:
cmd += " " + set_value_args
if values_files:
for values_file in values_files:
cmd += " -f=" + values_file
if reuse_values:
cmd += " --reuse-values"
rc, out, err = module.run_helm_command(cmd)
return (len(out.strip()) > 0, out.strip())
rc, out, err = run_helm(module, cmd)
return len(out.strip()) > 0
def default_check(release_status, chart_info, values=None, values_files=None):
@@ -685,73 +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.
"""
# the 'appVersion' specification is optional in a chart
chart_app_version = chart_info.get("appVersion", None)
released_app_version = release_status.get("app_version", None)
chart_app_version = chart_info.get('appVersion', None)
released_app_version = release_status.get('app_version', None)
# when deployed without an 'appVersion' chart value the 'helm list' command will return the entry `app_version: ""`
appversion_is_same = (chart_app_version == released_app_version) or (
chart_app_version is None and released_app_version == ""
)
appversion_is_same = (chart_app_version == released_app_version) or (chart_app_version is None and released_app_version == "")
if values_files:
values_match = release_status["values"] == load_values_files(values_files)
values_match = release_status['values'] == load_values_files(values_files)
else:
values_match = release_status["values"] == values
return (
not values_match
or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
values_match = release_status['values'] == values
return not values_match \
or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"] \
or not appversion_is_same
)
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
chart_ref=dict(type="path"),
chart_repo_url=dict(type="str"),
chart_version=dict(type="str"),
dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
release_name=dict(type="str", required=True, aliases=["name"]),
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
release_state=dict(
default="present", choices=["present", "absent"], aliases=["state"]
),
release_values=dict(type="dict", default={}, aliases=["values"]),
values_files=dict(type="list", default=[], elements="str"),
update_repo_cache=dict(type="bool", default=False),
disable_hook=dict(type="bool", default=False),
force=dict(type="bool", default=False),
purge=dict(type="bool", default=True),
wait=dict(type="bool", default=False),
wait_timeout=dict(type="str"),
timeout=dict(type="str"),
atomic=dict(type="bool", default=False),
create_namespace=dict(type="bool", default=False),
post_renderer=dict(type="str"),
replace=dict(type="bool", default=False),
skip_crds=dict(type="bool", default=False),
history_max=dict(type="int"),
set_values=dict(type="list", elements="dict"),
reuse_values=dict(type="bool"),
reset_values=dict(type="bool", default=True),
)
)
return arg_spec
def main():
global module
module = AnsibleHelmModule(
argument_spec=argument_spec(),
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type='path'),
chart_ref=dict(type='path'),
chart_repo_url=dict(type='str'),
chart_version=dict(type='str'),
release_name=dict(type='str', required=True, aliases=['name']),
release_namespace=dict(type='str', required=True, aliases=['namespace']),
release_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
release_values=dict(type='dict', default={}, aliases=['values']),
values_files=dict(type='list', default=[], elements='str'),
update_repo_cache=dict(type='bool', default=False),
# Helm options
disable_hook=dict(type='bool', default=False),
force=dict(type='bool', default=False),
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
purge=dict(type='bool', default=True),
wait=dict(type='bool', default=False),
wait_timeout=dict(type='str'),
atomic=dict(type='bool', default=False),
create_namespace=dict(type='bool', default=False),
replace=dict(type='bool', default=False),
skip_crds=dict(type='bool', default=False),
# Generic auth key
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
),
required_if=[
("release_state", "present", ["release_name", "chart_ref"]),
("release_state", "absent", ["release_name"]),
('release_state', 'present', ['release_name', 'chart_ref']),
('release_state', 'absent', ['release_name'])
],
mutually_exclusive=[
("context", "ca_cert"),
("replace", "history_max"),
("wait_timeout", "timeout"),
("kubeconfig", "ca_cert"),
],
supports_check_mode=True,
)
@@ -761,62 +555,48 @@ def main():
changed = False
chart_ref = module.params.get("chart_ref")
chart_repo_url = module.params.get("chart_repo_url")
chart_version = module.params.get("chart_version")
dependency_update = module.params.get("dependency_update")
release_name = module.params.get("release_name")
release_state = module.params.get("release_state")
release_values = module.params.get("release_values")
values_files = module.params.get("values_files")
update_repo_cache = module.params.get("update_repo_cache")
bin_path = module.params.get('binary_path')
chart_ref = module.params.get('chart_ref')
chart_repo_url = module.params.get('chart_repo_url')
chart_version = module.params.get('chart_version')
release_name = module.params.get('release_name')
release_state = module.params.get('release_state')
release_values = module.params.get('release_values')
values_files = module.params.get('values_files')
update_repo_cache = module.params.get('update_repo_cache')
# Helm options
disable_hook = module.params.get("disable_hook")
force = module.params.get("force")
purge = module.params.get("purge")
wait = module.params.get("wait")
wait_timeout = module.params.get("wait_timeout")
atomic = module.params.get("atomic")
create_namespace = module.params.get("create_namespace")
post_renderer = module.params.get("post_renderer")
replace = module.params.get("replace")
skip_crds = module.params.get("skip_crds")
history_max = module.params.get("history_max")
timeout = module.params.get("timeout")
set_values = module.params.get("set_values")
reuse_values = module.params.get("reuse_values")
reset_values = module.params.get("reset_values")
disable_hook = module.params.get('disable_hook')
force = module.params.get('force')
purge = module.params.get('purge')
wait = module.params.get('wait')
wait_timeout = module.params.get('wait_timeout')
atomic = module.params.get('atomic')
create_namespace = module.params.get('create_namespace')
replace = module.params.get('replace')
skip_crds = module.params.get('skip_crds')
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = module.get_bin_path('helm', required=True)
if update_repo_cache:
run_repo_update(module)
run_repo_update(module, helm_cmd_common)
# Get real/deployed release status
all_status = release_state == "absent"
release_status = get_release_status(module, release_name, all_status=all_status)
release_status = get_release_status(module, helm_cmd_common, release_name)
helm_cmd = module.get_helm_binary()
opt_result = {}
# keep helm_cmd_common for get_release_status in module_exit_json
helm_cmd = helm_cmd_common
if release_state == "absent" and release_status is not None:
# skip release statuses 'uninstalled' and 'uninstalling'
if not release_status["status"].startswith("uninstall"):
if replace:
module.fail_json(msg="replace is not applicable when state is absent")
if replace:
module.fail_json(msg="replace is not applicable when state is absent")
if wait:
helm_version = module.get_helm_version()
if LooseVersion(helm_version) < LooseVersion("3.7.0"):
opt_result["warnings"] = []
opt_result["warnings"].append(
"helm uninstall support option --wait for helm release >= 3.7.0"
)
wait = False
helm_cmd = delete(
helm_cmd, release_name, purge, disable_hook, wait, wait_timeout
)
changed = True
helm_cmd = delete(helm_cmd, release_name, purge, disable_hook)
changed = True
elif release_state == "present":
if chart_version is not None:
helm_cmd += " --version=" + chart_version
@@ -826,161 +606,67 @@ def main():
# Fetch chart info to have real version and real name for chart_ref from archive, folder or url
chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
if dependency_update:
if chart_info.get("dependencies"):
# Can't use '--dependency-update' with 'helm upgrade' that is the
# default chart install method, so if chart_repo_url is defined
# we can't use the dependency update command. But, in the near future
# we can get rid of this method and use only '--dependency-update'
# option. Please see https://github.com/helm/helm/pull/8810
if not chart_repo_url and not re.fullmatch(
r"^http[s]*://[\w.:/?&=-]+$", chart_ref
):
run_dep_update(module, chart_ref)
# To not add --dependency-update option in the deploy function
dependency_update = False
else:
module.warn(
"This is a not stable feature with 'chart_repo_url'. Please consider to use dependency update with on-disk charts"
)
if not replace:
msg_fail = (
"'--dependency-update' hasn't been supported yet with 'helm upgrade'. "
"Please use 'helm install' instead by adding 'replace' option"
)
module.fail_json(msg=msg_fail)
else:
module.warn(
"There is no dependencies block defined in Chart.yaml. Dependency update will not be performed. "
"Please consider add dependencies block or disable dependency_update to remove this warning."
)
set_value_args = None
if set_values:
set_value_args = module.get_helm_set_values_args(set_values)
if release_status is None: # Not installed
helm_cmd = deploy(
module,
helm_cmd,
release_name,
release_values,
chart_ref,
wait,
wait_timeout,
disable_hook,
False,
values_files=values_files,
atomic=atomic,
create_namespace=create_namespace,
post_renderer=post_renderer,
replace=replace,
dependency_update=dependency_update,
skip_crds=skip_crds,
history_max=history_max,
timeout=timeout,
set_value_args=set_value_args,
reuse_values=reuse_values,
reset_values=reset_values,
)
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
disable_hook, False, values_files=values_files, atomic=atomic,
create_namespace=create_namespace, replace=replace,
skip_crds=skip_crds)
changed = True
else:
helm_diff_version = get_plugin_version("diff")
if helm_diff_version and (
not chart_repo_url
or (
chart_repo_url
and LooseVersion(helm_diff_version) >= LooseVersion("3.4.1")
)
):
(would_change, prepared) = helmdiff_check(
module,
release_name,
chart_ref,
release_values,
values_files,
chart_version,
replace,
chart_repo_url,
post_renderer,
set_value_args,
reuse_values=reuse_values,
reset_values=reset_values,
)
if would_change and module._diff:
opt_result["diff"] = {"prepared": prepared}
if has_plugin(helm_cmd_common, "diff") and not chart_repo_url:
would_change = helmdiff_check(module, helm_cmd_common, release_name, chart_ref,
release_values, values_files, chart_version, replace)
else:
module.warn(
"The default idempotency check can fail to report changes in certain cases. "
"Install helm diff >= 3.4.1 for better results."
)
would_change = default_check(
release_status, chart_info, release_values, values_files
)
module.warn("The default idempotency check can fail to report changes in certain cases. "
"Install helm diff for better results.")
would_change = default_check(release_status, chart_info, release_values, values_files)
if force or would_change:
helm_cmd = deploy(
module,
helm_cmd,
release_name,
release_values,
chart_ref,
wait,
wait_timeout,
disable_hook,
force,
values_files=values_files,
atomic=atomic,
create_namespace=create_namespace,
post_renderer=post_renderer,
replace=replace,
skip_crds=skip_crds,
history_max=history_max,
timeout=timeout,
dependency_update=dependency_update,
set_value_args=set_value_args,
reuse_values=reuse_values,
reset_values=reset_values,
)
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
disable_hook, force, values_files=values_files, atomic=atomic,
create_namespace=create_namespace, replace=replace,
skip_crds=skip_crds)
changed = True
if module.check_mode:
check_status = {"values": {"current": {}, "declared": {}}}
check_status = {
'values': {
"current": {},
"declared": {},
}
}
if release_status:
check_status["values"]["current"] = release_status["values"]
check_status["values"]["declared"] = release_status
check_status['values']['current'] = release_status['values']
check_status['values']['declared'] = release_status
module.exit_json(
changed=changed,
command=helm_cmd,
status=check_status,
stdout="",
stderr="",
**opt_result,
stdout='',
stderr='',
)
elif not changed:
module.exit_json(
changed=False,
status=release_status,
stdout="",
stderr="",
stdout='',
stderr='',
command=helm_cmd,
**opt_result,
)
rc, out, err = module.run_helm_command(helm_cmd)
rc, out, err = run_helm(module, helm_cmd)
module.exit_json(
changed=changed,
stdout=out,
stderr=err,
status=get_release_status(module, release_name, all_status=True),
status=get_release_status(module, helm_cmd_common, release_name),
command=helm_cmd,
**opt_result,
)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -4,17 +4,16 @@
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
---
module: helm_info
short_description: Get information from Helm package deployed inside the cluster
version_added: 0.11.0
version_added: "0.11.0"
author:
- Lucas Boisserie (@LucasBoisserie)
@@ -39,48 +38,18 @@ options:
required: true
type: str
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
default: []
version_added: 2.3.0
get_all_values:
description:
- Set to C(True) if you want to get all (computed) values of the release.
- When C(False) (default), only user supplied values are returned.
required: false
default: false
type: bool
version_added: 2.4.0
extends_documentation_fragment:
- kubernetes.core.helm_common_options
"""
'''
EXAMPLES = r"""
- name: Gather information of Grafana chart inside monitoring namespace
EXAMPLES = r'''
- name: Deploy latest version of Grafana chart inside monitoring namespace
kubernetes.core.helm_info:
name: test
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 = r"""
RETURN = r'''
status:
type: complex
description: A dictionary of status output
@@ -118,83 +87,40 @@ status:
type: str
returned: always
description: Dict of Values used to deploy
hooks:
type: list
elements: dict
description: Hooks of the release
returned: always
version_added: 2.4.0
notes:
type: str
description: Notes of the release
returned: always
version_added: 2.4.0
manifest:
type: list
elements: dict
description: Manifest of the release
returned: always
version_added: 2.4.0
"""
'''
import copy
import traceback
try:
import yaml
IMP_YAML = True
IMP_YAML_ERR = None
except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm, get_values
# Get Release from all deployed releases
def get_release(state, release_name):
if state is not None:
for release in state:
if release["name"] == release_name:
if release['name'] == release_name:
return release
return None
# Get Release state from deployed release
def get_release_status(module, release_name, release_state, get_all_values=False):
list_command = module.get_helm_binary() + " list --output=yaml"
def get_release_status(module, command, release_name):
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 = module.run_helm_command(list_command)
rc, out, err = run_helm(module, list_command)
if rc != 0:
module.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
rc, out, err
),
command=list_command,
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
command=list_command
)
release = get_release(yaml.safe_load(out), release_name)
@@ -202,46 +128,51 @@ def get_release_status(module, release_name, release_state, get_all_values=False
if release is None: # not install
return None
release["values"] = module.get_values(release_name, get_all_values)
release["manifest"] = module.get_manifest(release_name)
release["notes"] = module.get_notes(release_name)
release["hooks"] = module.get_hooks(release_name)
release['values'] = get_values(module, command, release_name)
return release
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
release_name=dict(type="str", required=True, aliases=["name"]),
release_namespace=dict(type="str", required=True, aliases=["namespace"]),
release_state=dict(type="list", default=[], elements="str"),
get_all_values=dict(type="bool", required=False, default=False),
)
)
return arg_spec
def main():
global module
module = AnsibleHelmModule(
argument_spec=argument_spec(),
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type='path'),
release_name=dict(type='str', required=True, aliases=['name']),
release_namespace=dict(type='str', required=True, aliases=['namespace']),
# Helm options
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
# Generic auth key
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
),
mutually_exclusive=[
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs")
],
supports_check_mode=True,
)
if not IMP_YAML:
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
release_name = module.params.get("release_name")
release_state = module.params.get("release_state")
get_all_values = module.params.get("get_all_values")
bin_path = module.params.get('binary_path')
release_name = module.params.get('release_name')
release_status = get_release_status(
module, release_name, release_state, get_all_values
)
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = module.get_bin_path('helm', required=True)
release_status = get_release_status(module, helm_cmd_common, release_name)
if release_status is not None:
module.exit_json(changed=False, status=release_status)
@@ -249,5 +180,5 @@ def main():
module.exit_json(changed=False)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -8,11 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
---
module: helm_plugin
short_description: Manage Helm plugins
version_added: 1.0.0
version_added: "1.0.0"
author:
- Abhijeet Kasurde (@Akasurde)
requirements:
@@ -24,15 +24,14 @@ options:
state:
description:
- 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.
choices: [ absent, present, latest ]
choices: [ absent, present ]
default: present
type: str
plugin_name:
description:
- Name of Helm plugin.
- Required only if C(state=absent) or C(state=latest).
- Required only if C(state=absent).
type: str
plugin_path:
description:
@@ -41,18 +40,11 @@ options:
machine and not on Ansible controller.
- Required only if C(state=present).
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:
- kubernetes.core.helm_common_options
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Install Helm env plugin
kubernetes.core.helm_plugin:
plugin_path: https://github.com/adamreese/helm-env
@@ -67,20 +59,9 @@ EXAMPLES = r"""
kubernetes.core.helm_plugin:
plugin_name: env
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 = r"""
RETURN = r'''
stdout:
type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log
@@ -106,78 +87,67 @@ rc:
description: Helm plugin command return code
returned: always
sample: 1
"""
import copy
'''
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
parse_helm_plugin_list,
run_helm,
get_helm_plugin_list,
parse_helm_plugin_list
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
plugin_path=dict(
type="str",
),
plugin_name=dict(
type="str",
),
plugin_version=dict(
type="str",
),
state=dict(
type="str",
default="present",
choices=["present", "absent", "latest"],
),
)
)
return arg_spec
def mutually_exclusive():
mutually_ex = copy.deepcopy(HELM_AUTH_MUTUALLY_EXCLUSIVE)
mutually_ex.append(("plugin_name", "plugin_path"))
return mutually_ex
def main():
module = AnsibleHelmModule(
argument_spec=argument_spec(),
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type='path'),
state=dict(type='str', default='present', choices=['present', 'absent']),
plugin_path=dict(type='str',),
plugin_name=dict(type='str',),
# Helm options
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
# Generic auth key
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
),
supports_check_mode=True,
required_if=[
("state", "present", ("plugin_path",)),
("state", "absent", ("plugin_name",)),
("state", "latest", ("plugin_name",)),
],
mutually_exclusive=mutually_exclusive(),
mutually_exclusive=[
('plugin_name', 'plugin_path'),
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs")
],
)
state = module.params.get("state")
bin_path = module.params.get('binary_path')
state = module.params.get('state')
helm_cmd_common = module.get_helm_binary() + " plugin"
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = 'helm'
if state == "present":
helm_cmd_common += " 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
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
helm_cmd_common += " plugin"
if state == 'present':
helm_cmd_common += " install %s" % module.params.get('plugin_path')
if not module.check_mode:
rc, out, err = module.run_helm_command(
helm_cmd_common, fails_on_error=False
)
rc, out, err = run_helm(module, helm_cmd_common, fails_on_error=False)
else:
rc, out, err = (0, "", "")
rc, out, err = (0, '', '')
if rc == 1 and "plugin already exists" in err:
if rc == 1 and 'plugin already exists' in err:
module.exit_json(
failed=False,
changed=False,
@@ -185,7 +155,7 @@ def main():
command=helm_cmd_common,
stdout=out,
stderr=err,
rc=rc,
rc=rc
)
elif rc == 0:
module.exit_json(
@@ -205,20 +175,20 @@ def main():
stderr=err,
rc=rc,
)
elif state == "absent":
plugin_name = module.params.get("plugin_name")
rc, output, err, command = module.get_helm_plugin_list()
out = parse_helm_plugin_list(output=output.splitlines())
elif state == 'absent':
plugin_name = module.params.get('plugin_name')
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
out = parse_helm_plugin_list(module, output=output.splitlines())
if not out:
module.exit_json(
failed=False,
changed=False,
msg="Plugin not found or is already uninstalled",
command=command,
command=helm_cmd_common + " list",
stdout=output,
stderr=err,
rc=rc,
rc=rc
)
found = False
@@ -231,19 +201,17 @@ def main():
failed=False,
changed=False,
msg="Plugin not found or is already uninstalled",
command=command,
command=helm_cmd_common + " list",
stdout=output,
stderr=err,
rc=rc,
rc=rc
)
helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
if not module.check_mode:
rc, out, err = module.run_helm_command(
helm_uninstall_cmd, fails_on_error=False
)
rc, out, err = run_helm(module, helm_uninstall_cmd, fails_on_error=False)
else:
rc, out, err = (0, "", "")
rc, out, err = (0, '', '')
if rc == 0:
module.exit_json(
@@ -252,7 +220,7 @@ def main():
command=helm_uninstall_cmd,
stdout=out,
stderr=err,
rc=rc,
rc=rc
)
module.fail_json(
msg="Failed to get Helm plugin uninstall",
@@ -261,63 +229,7 @@ def main():
stderr=err,
rc=rc,
)
elif state == "latest":
plugin_name = module.params.get("plugin_name")
rc, output, err, command = module.get_helm_plugin_list()
out = parse_helm_plugin_list(output=output.splitlines())
if not out:
module.exit_json(
failed=False,
changed=False,
msg="Plugin not found",
command=command,
stdout=output,
stderr=err,
rc=rc,
)
found = False
for line in out:
if line[0] == plugin_name:
found = True
break
if not found:
module.exit_json(
failed=False,
changed=False,
msg="Plugin not found",
command=command,
stdout=output,
stderr=err,
rc=rc,
)
helm_update_cmd = "%s update %s" % (helm_cmd_common, plugin_name)
if not module.check_mode:
rc, out, err = module.run_helm_command(
helm_update_cmd, fails_on_error=False
)
else:
rc, out, err = (0, "", "")
if rc == 0:
module.exit_json(
changed=True,
msg="Plugin updated successfully",
command=helm_update_cmd,
stdout=out,
stderr=err,
rc=rc,
)
module.fail_json(
msg="Failed to get Helm plugin update",
command=helm_update_cmd,
stdout=out,
stderr=err,
rc=rc,
)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -8,11 +8,11 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
---
module: helm_plugin_info
short_description: Gather information about Helm plugins
version_added: 1.0.0
version_added: "1.0.0"
author:
- Abhijeet Kasurde (@Akasurde)
requirements:
@@ -27,18 +27,18 @@ options:
type: str
extends_documentation_fragment:
- kubernetes.core.helm_common_options
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Gather Helm plugin info
kubernetes.core.helm_plugin_info:
- name: Gather Helm env plugin info
kubernetes.core.helm_plugin_info:
plugin_name: env
"""
'''
RETURN = r"""
RETURN = r'''
stdout:
type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log
@@ -68,60 +68,82 @@ rc:
description: Helm plugin command return code
returned: always
sample: 1
"""
import copy
'''
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
get_helm_plugin_list,
parse_helm_plugin_list,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
def main():
argument_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
argument_spec.update(
dict(
plugin_name=dict(
type="str",
),
)
)
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type='path'),
plugin_name=dict(type='str',),
# Helm options
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
module = AnsibleHelmModule(
argument_spec=argument_spec,
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
# Generic auth key
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
ca_cert=dict(type='path', aliases=['ssl_ca_cert'], fallback=(env_fallback, ['K8S_AUTH_SSL_CA_CERT'])),
validate_certs=dict(type='bool', default=True, aliases=['verify_ssl'], fallback=(env_fallback, ['K8S_AUTH_VERIFY_SSL'])),
api_key=dict(type='str', no_log=True, fallback=(env_fallback, ['K8S_AUTH_API_KEY']))
),
mutually_exclusive=[
("context", "ca_cert"),
("context", "validate_certs"),
("kubeconfig", "ca_cert"),
("kubeconfig", "validate_certs")
],
supports_check_mode=True,
)
plugin_name = module.params.get("plugin_name")
bin_path = module.params.get('binary_path')
if bin_path is not None:
helm_cmd_common = bin_path
else:
helm_cmd_common = 'helm'
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
helm_cmd_common += " plugin"
plugin_name = module.params.get('plugin_name')
plugin_list = []
rc, output, err, command = module.get_helm_plugin_list()
rc, output, err = get_helm_plugin_list(module, helm_bin=helm_cmd_common)
out = parse_helm_plugin_list(output=output.splitlines())
out = parse_helm_plugin_list(module, output=output.splitlines())
for line in out:
if plugin_name is None:
plugin_list.append(
{"name": line[0], "version": line[1], "description": line[2]}
{
"name": line[0],
"version": line[1],
"description": line[2],
}
)
continue
if plugin_name == line[0]:
plugin_list.append(
{"name": line[0], "version": line[1], "description": line[2]}
{
"name": line[0],
"version": line[1],
"description": line[2],
}
)
break
module.exit_json(
changed=True,
command=command,
command=helm_cmd_common + " list",
stdout=output,
stderr=err,
rc=rc,
@@ -129,5 +151,5 @@ def main():
)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,302 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
---
module: helm_pull
short_description: download a chart from a repository and (optionally) unpack it in local directory.
version_added: 2.4.0
author:
- Aubin Bikouo (@abikouo)
description:
- Retrieve a package from a package repository, and download it locally.
- It can also be used to perform cryptographic verification of a chart without installing the chart.
- There are options for unpacking the chart after download.
requirements:
- "helm >= 3.0 (https://github.com/helm/helm/releases)"
options:
chart_ref:
description:
- chart name on chart repository.
- absolute URL.
required: true
type: str
chart_version:
description:
- Specify a version constraint for the chart version to use.
- This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).
- Mutually exclusive with C(chart_devel).
type: str
verify_chart:
description:
- Verify the package before using it.
default: False
type: bool
verify_chart_keyring:
description:
- location of public keys used for verification.
type: path
provenance:
description:
- Fetch the provenance file, but don't perform verification.
type: bool
default: False
repo_url:
description:
- chart repository url where to locate the requested chart.
type: str
aliases: [ url, chart_repo_url ]
repo_username:
description:
- Chart repository username where to locate the requested chart.
- Required if C(repo_password) is specified.
type: str
aliases: [ username, chart_repo_username ]
repo_password:
description:
- Chart repository password where to locate the requested chart.
- Required if C(repo_username) is specified.
type: str
aliases: [ password, chart_repo_password ]
pass_credentials:
description:
- Pass credentials to all domains.
default: False
type: bool
skip_tls_certs_check:
description:
- Whether or not to check tls certificate for the chart download.
- Requires helm >= 3.3.0.
type: bool
default: False
chart_devel:
description:
- Use development versions, too. Equivalent to version '>0.0.0-0'.
- Mutually exclusive with C(chart_version).
type: bool
untar_chart:
description:
- if set to true, will untar the chart after downloading it.
type: bool
default: False
destination:
description:
- location to write the chart.
type: path
required: True
chart_ca_cert:
description:
- Verify certificates of HTTPS-enabled servers using this CA bundle.
- Requires helm >= 3.1.0.
type: path
chart_ssl_cert_file:
description:
- Identify HTTPS client using this SSL certificate file.
- Requires helm >= 3.1.0.
type: path
chart_ssl_key_file:
description:
- Identify HTTPS client using this SSL key file
- Requires helm >= 3.1.0.
type: path
binary_path:
description:
- The path of a helm binary to use.
required: false
type: path
"""
EXAMPLES = r"""
- name: Download chart using chart url
kubernetes.core.helm_pull:
chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
destination: /path/to/chart
- name: Download Chart using chart_name and repo_url
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
- name: Download Chart (skip tls certificate check)
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
skip_tls_certs_check: yes
- name: Download Chart using chart registry credentials
kubernetes.core.helm_pull:
chart_ref: redis
repo_url: https://charts.bitnami.com/bitnami
untar_chart: yes
destination: /path/to/chart
username: myuser
password: mypassword123
"""
RETURN = r"""
stdout:
type: str
description: Full `helm pull` command stdout, in case you want to display it or examine the event log
returned: always
sample: ''
stderr:
type: str
description: Full `helm pull` command stderr, in case you want to display it or examine the event log
returned: always
sample: ''
command:
type: str
description: Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.
returned: always
sample: helm pull --repo test ...
rc:
type: int
description: Helm pull command return code
returned: always
sample: 1
"""
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)
def main():
argspec = dict(
chart_ref=dict(type="str", required=True),
chart_version=dict(type="str"),
verify_chart=dict(type="bool", default=False),
verify_chart_keyring=dict(type="path"),
provenance=dict(type="bool", default=False),
repo_url=dict(type="str", aliases=["url", "chart_repo_url"]),
repo_username=dict(type="str", aliases=["username", "chart_repo_username"]),
repo_password=dict(
type="str", no_log=True, aliases=["password", "chart_repo_password"]
),
pass_credentials=dict(type="bool", default=False),
skip_tls_certs_check=dict(type="bool", default=False),
chart_devel=dict(type="bool"),
untar_chart=dict(type="bool", default=False),
destination=dict(type="path", required=True),
chart_ca_cert=dict(type="path"),
chart_ssl_cert_file=dict(type="path"),
chart_ssl_key_file=dict(type="path"),
binary_path=dict(type="path"),
)
module = AnsibleHelmModule(
argument_spec=argspec,
supports_check_mode=True,
required_by=dict(
repo_username=("repo_password"),
repo_password=("repo_username"),
),
mutually_exclusive=[("chart_version", "chart_devel")],
)
helm_version = module.get_helm_version()
if LooseVersion(helm_version) < LooseVersion("3.0.0"):
module.fail_json(
msg="This module requires helm >= 3.0.0, current version is {0}".format(
helm_version
)
)
helm_pull_opt_versionning = dict(
skip_tls_certs_check="3.3.0",
chart_ca_cert="3.1.0",
chart_ssl_cert_file="3.1.0",
chart_ssl_key_file="3.1.0",
)
def test_version_requirement(opt):
req_version = helm_pull_opt_versionning.get(opt)
if req_version and LooseVersion(helm_version) < LooseVersion(req_version):
module.fail_json(
msg="Parameter {0} requires helm >= {1}, current version is {2}".format(
opt, req_version, helm_version
)
)
# Set `helm pull` arguments requiring values
helm_pull_opts = []
helm_value_args = dict(
chart_version="version",
verify_chart_keyring="keyring",
repo_url="repo",
repo_username="username",
repo_password="password",
destination="destination",
chart_ca_cert="ca-file",
chart_ssl_cert_file="cert-file",
chart_ssl_key_file="key-file",
)
for opt, cmdkey in helm_value_args.items():
if module.params.get(opt):
test_version_requirement(opt)
helm_pull_opts.append("--{0} {1}".format(cmdkey, module.params.get(opt)))
# Set `helm pull` arguments flags
helm_flag_args = dict(
verify_chart=dict(key="verify"),
provenance=dict(key="prov"),
pass_credentials=dict(key="pass-credentials"),
skip_tls_certs_check=dict(key="insecure-skip-tls-verify"),
chart_devel=dict(key="devel"),
untar_chart=dict(key="untar"),
)
for k, v in helm_flag_args.items():
if module.params.get(k):
test_version_requirement(k)
helm_pull_opts.append("--{0}".format(v["key"]))
helm_cmd_common = "{0} pull {1} {2}".format(
module.get_helm_binary(),
module.params.get("chart_ref"),
" ".join(helm_pull_opts),
)
if not module.check_mode:
rc, out, err = module.run_helm_command(helm_cmd_common, fails_on_error=False)
else:
rc, out, err = (0, "", "")
if rc == 0:
module.exit_json(
failed=False,
changed=True,
command=helm_cmd_common,
stdout=out,
stderr=err,
rc=rc,
)
else:
module.fail_json(
msg="Failure when executing Helm command.",
command=helm_cmd_common,
changed=False,
stdout=out,
stderr=err,
rc=rc,
)
if __name__ == "__main__":
main()

View File

@@ -4,17 +4,16 @@
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
---
module: helm_repository
short_description: Manage Helm repositories.
version_added: 0.11.0
version_added: "0.11.0"
author:
- Lucas Boisserie (@LucasBoisserie)
@@ -65,63 +64,9 @@ options:
default: present
aliases: [ state ]
type: str
pass_credentials:
description:
- Pass credentials to all domains.
required: false
default: false
type: bool
version_added: 2.3.0
host:
description:
- Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
type: str
version_added: 2.3.0
api_key:
description:
- Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable.
type: str
version_added: 2.3.0
validate_certs:
description:
- Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL)
environment variable.
type: bool
aliases: [ verify_ssl ]
default: True
version_added: 2.3.0
ca_cert:
description:
- Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable.
type: path
aliases: [ ssl_ca_cert ]
version_added: 2.3.0
context:
description:
- Helm option to specify which kubeconfig context to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
type: str
aliases: [ kube_context ]
version_added: 2.4.0
kubeconfig:
description:
- Helm option to specify kubeconfig path to use.
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
- The configuration can be provided as dictionary.
type: raw
aliases: [ kubeconfig_path ]
version_added: 2.4.0
force_update:
description:
- Whether or not to replace (overwrite) the repo if it already exists.
type: bool
aliases: [ force ]
default: False
version_added: 2.4.0
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Add a repository
kubernetes.core.helm_repository:
name: stable
@@ -131,9 +76,9 @@ EXAMPLES = r"""
kubernetes.core.helm_repository:
name: redhat-charts
repo_url: https://redhat-developer.github.com/redhat-helm-charts
"""
'''
RETURN = r"""
RETURN = r'''
stdout:
type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log
@@ -164,81 +109,56 @@ msg:
description: Error message returned by `helm` command
returned: on failure
sample: 'Repository already have a repository named bitnami'
"""
'''
import copy
import traceback
try:
import yaml
IMP_YAML = True
IMP_YAML_ERR = None
except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
HELM_AUTH_ARG_SPEC,
HELM_AUTH_MUTUALLY_EXCLUSIVE,
)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
# Get repository from all repositories added
def get_repository(state, repo_name):
if state is not None:
for repository in state:
if repository["name"] == repo_name:
if repository['name'] == repo_name:
return repository
return None
# Get repository status
def get_repository_status(module, repository_name):
list_command = module.get_helm_binary() + " repo list --output=yaml"
def get_repository_status(module, command, repository_name):
list_command = command + " repo list --output=yaml"
rc, out, err = module.run_helm_command(list_command, fails_on_error=False)
rc, out, err = run_helm(module, list_command, fails_on_error=False)
# no repo => rc=1 and 'no repositories to show' in output
if rc == 1 and "no repositories to show" in err:
return None
elif rc != 0:
module.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
rc, out, err
),
command=list_command,
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
command=list_command
)
return get_repository(yaml.safe_load(out), repository_name)
# Install repository
def install_repository(
command,
repository_name,
repository_url,
repository_username,
repository_password,
pass_credentials,
force_update,
):
def install_repository(command, repository_name, repository_url, repository_username, repository_password):
install_command = command + " repo add " + repository_name + " " + repository_url
if repository_username is not None and repository_password is not None:
install_command += " --username=" + repository_username
install_command += " --password=" + repository_password
if pass_credentials:
install_command += " --pass-credentials"
if force_update:
install_command += " --force-update"
return install_command
@@ -249,32 +169,24 @@ def delete_repository(command, repository_name):
return remove_command
def argument_spec():
arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
arg_spec.update(
dict(
repo_name=dict(type="str", aliases=["name"], required=True),
repo_url=dict(type="str", aliases=["url"]),
repo_username=dict(type="str", aliases=["username"]),
repo_password=dict(type="str", aliases=["password"], no_log=True),
repo_state=dict(
default="present", choices=["present", "absent"], aliases=["state"]
),
pass_credentials=dict(type="bool", default=False, no_log=True),
force_update=dict(type="bool", default=False, aliases=["force"]),
)
)
return arg_spec
def main():
global module
module = AnsibleHelmModule(
argument_spec=argument_spec(),
required_together=[["repo_username", "repo_password"]],
required_if=[("repo_state", "present", ["repo_url"])],
mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type='path'),
repo_name=dict(type='str', aliases=['name'], required=True),
repo_url=dict(type='str', aliases=['url']),
repo_username=dict(type='str', aliases=['username']),
repo_password=dict(type='str', aliases=['password'], no_log=True),
repo_state=dict(default='present', choices=['present', 'absent'], aliases=['state']),
),
required_together=[
['repo_username', 'repo_password']
],
required_if=[
('repo_state', 'present', ['repo_url']),
],
supports_check_mode=True,
)
@@ -283,58 +195,48 @@ def main():
changed = False
repo_name = module.params.get("repo_name")
repo_url = module.params.get("repo_url")
repo_username = module.params.get("repo_username")
repo_password = module.params.get("repo_password")
repo_state = module.params.get("repo_state")
pass_credentials = module.params.get("pass_credentials")
force_update = module.params.get("force_update")
bin_path = module.params.get('binary_path')
repo_name = module.params.get('repo_name')
repo_url = module.params.get('repo_url')
repo_username = module.params.get('repo_username')
repo_password = module.params.get('repo_password')
repo_state = module.params.get('repo_state')
helm_cmd = module.get_helm_binary()
if bin_path is not None:
helm_cmd = bin_path
else:
helm_cmd = module.get_bin_path('helm', required=True)
repository_status = get_repository_status(module, repo_name)
repository_status = get_repository_status(module, helm_cmd, repo_name)
if repo_state == "absent" and repository_status is not None:
helm_cmd = delete_repository(helm_cmd, repo_name)
changed = True
elif repo_state == "present":
if repository_status is None or force_update:
helm_cmd = install_repository(
helm_cmd,
repo_name,
repo_url,
repo_username,
repo_password,
pass_credentials,
force_update,
)
if repository_status is None:
helm_cmd = install_repository(helm_cmd, repo_name, repo_url, repo_username, repo_password)
changed = True
elif repository_status["url"] != repo_url:
module.fail_json(
msg="Repository already have a repository named {0}".format(repo_name)
)
elif repository_status['url'] != repo_url:
module.fail_json(msg="Repository already have a repository named {0}".format(repo_name))
if module.check_mode:
module.exit_json(changed=changed)
elif not changed:
module.exit_json(changed=False, repo_name=repo_name, repo_url=repo_url)
rc, out, err = module.run_helm_command(helm_cmd)
rc, out, err = run_helm(module, helm_cmd)
if repo_password is not None:
helm_cmd = helm_cmd.replace(repo_password, "******")
helm_cmd = helm_cmd.replace(repo_password, '******')
if rc != 0:
module.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
rc, out, err
),
command=helm_cmd,
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
command=helm_cmd
)
module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -9,7 +9,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
module: helm_template
@@ -45,21 +45,6 @@ options:
- Chart version to use. If this is not specified, the latest version is installed.
required: false
type: str
dependency_update:
description:
- Run helm dependency update before the operation.
- The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
- For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
default: false
type: bool
aliases: [ dep_up ]
version_added: 2.4.0
disable_hook:
description:
- Prevent hooks from running during install.
default: False
type: bool
version_added: 2.4.0
include_crds:
description:
- Include custom resource descriptions in rendered templates.
@@ -72,19 +57,6 @@ options:
- If the directory already exists, it will be overwritten.
required: false
type: path
release_name:
description:
- Release name to use in rendered templates.
required: false
aliases: [ name ]
type: str
version_added: 2.4.0
release_namespace:
description:
- namespace scope for this request.
required: false
type: str
version_added: 2.4.0
release_values:
description:
- Values to pass to chart.
@@ -92,14 +64,6 @@ options:
default: {}
aliases: [ values ]
type: dict
show_only:
description:
- Only show manifests rendered from the given templates.
required: false
type: list
elements: str
default: []
version_added: 2.4.0
values_files:
description:
- Value files to pass to chart.
@@ -115,34 +79,9 @@ options:
- Run C(helm repo update) before the operation. Can be run as part of the template generation or as a separate step.
default: false
type: bool
set_values:
description:
- Values to pass to chart configuration.
required: false
type: list
elements: dict
suboptions:
value:
description:
- Value to pass to chart configuration (e.g phase=prod).
type: str
required: true
value_type:
description:
- Use C(raw) set individual value.
- Use C(string) to force a string for an individual value.
- Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
- Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
default: raw
choices:
- raw
- string
- json
- file
version_added: 2.4.0
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Render templates to specified directory
kubernetes.core.helm_template:
chart_ref: stable/prometheus
@@ -157,27 +96,9 @@ EXAMPLES = r"""
copy:
dest: myfile.yaml
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 = r"""
RETURN = r'''
stdout:
type: str
description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents.
@@ -193,51 +114,25 @@ command:
description: Full C(helm) command run by this module, in case you want to re-run the command outside the module or debug a problem.
returned: always
sample: helm template --output-dir mychart nginx-stable/nginx-ingress
"""
'''
import tempfile
import traceback
try:
import yaml
IMP_YAML = True
IMP_YAML_ERR = None
except ImportError:
IMP_YAML_ERR = traceback.format_exc()
IMP_YAML = False
from ansible.module_utils.basic import missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
)
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.kubernetes.core.plugins.module_utils.helm import run_helm
def template(
cmd,
chart_ref,
chart_repo_url=None,
chart_version=None,
dependency_update=None,
disable_hook=None,
output_dir=None,
show_only=None,
release_name=None,
release_namespace=None,
release_values=None,
values_files=None,
include_crds=False,
set_values=None,
):
cmd += " template "
if release_name:
cmd += release_name + " "
cmd += chart_ref
if dependency_update:
cmd += " --dependency-update"
def template(cmd, chart_ref, chart_repo_url=None, chart_version=None, output_dir=None,
release_values=None, values_files=None, include_crds=False):
cmd += " template " + chart_ref
if chart_repo_url:
cmd += " --repo=" + chart_repo_url
@@ -245,116 +140,81 @@ def template(
if chart_version:
cmd += " --version=" + chart_version
if disable_hook:
cmd += " --no-hooks"
if output_dir:
cmd += " --output-dir=" + output_dir
if show_only:
for template in show_only:
cmd += " -s " + template
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 values_files:
for values_file in values_files:
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:
cmd += " --include-crds"
if set_values:
cmd += " " + set_values
return cmd
def main():
module = AnsibleHelmModule(
module = AnsibleModule(
argument_spec=dict(
binary_path=dict(type="path"),
chart_ref=dict(type="path", required=True),
chart_repo_url=dict(type="str"),
chart_version=dict(type="str"),
dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
disable_hook=dict(type="bool", default=False),
include_crds=dict(type="bool", default=False),
release_name=dict(type="str", aliases=["name"]),
output_dir=dict(type="path"),
release_namespace=dict(type="str"),
release_values=dict(type="dict", default={}, aliases=["values"]),
show_only=dict(type="list", default=[], elements="str"),
values_files=dict(type="list", default=[], elements="str"),
update_repo_cache=dict(type="bool", default=False),
set_values=dict(type="list", elements="dict"),
binary_path=dict(type='path'),
chart_ref=dict(type='path', required=True),
chart_repo_url=dict(type='str'),
chart_version=dict(type='str'),
include_crds=dict(type='bool', default=False),
output_dir=dict(type='path'),
release_values=dict(type='dict', default={}, aliases=['values']),
values_files=dict(type='list', default=[], elements='str'),
update_repo_cache=dict(type='bool', default=False)
),
supports_check_mode=True,
supports_check_mode=True
)
check_mode = module.check_mode
chart_ref = module.params.get("chart_ref")
chart_repo_url = module.params.get("chart_repo_url")
chart_version = module.params.get("chart_version")
dependency_update = module.params.get("dependency_update")
disable_hook = module.params.get("disable_hook")
include_crds = module.params.get("include_crds")
release_name = module.params.get("release_name")
output_dir = module.params.get("output_dir")
show_only = module.params.get("show_only")
release_namespace = module.params.get("release_namespace")
release_values = module.params.get("release_values")
values_files = module.params.get("values_files")
update_repo_cache = module.params.get("update_repo_cache")
set_values = module.params.get("set_values")
bin_path = module.params.get('binary_path')
chart_ref = module.params.get('chart_ref')
chart_repo_url = module.params.get('chart_repo_url')
chart_version = module.params.get('chart_version')
include_crds = module.params.get('include_crds')
output_dir = module.params.get('output_dir')
release_values = module.params.get('release_values')
values_files = module.params.get('values_files')
update_repo_cache = module.params.get('update_repo_cache')
if not IMP_YAML:
module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
helm_cmd = module.get_helm_binary()
helm_cmd = bin_path or module.get_bin_path('helm', required=True)
if update_repo_cache:
update_cmd = helm_cmd + " repo update"
module.run_helm_command(update_cmd)
run_helm(module, update_cmd)
set_values_args = None
if set_values:
set_values_args = module.get_helm_set_values_args(set_values)
tmpl_cmd = template(
helm_cmd,
chart_ref,
dependency_update=dependency_update,
chart_repo_url=chart_repo_url,
chart_version=chart_version,
disable_hook=disable_hook,
release_name=release_name,
output_dir=output_dir,
release_namespace=release_namespace,
release_values=release_values,
show_only=show_only,
values_files=values_files,
include_crds=include_crds,
set_values=set_values_args,
)
tmpl_cmd = template(helm_cmd, chart_ref, chart_repo_url=chart_repo_url,
chart_version=chart_version, output_dir=output_dir,
release_values=release_values, values_files=values_files,
include_crds=include_crds)
if not check_mode:
rc, out, err = module.run_helm_command(tmpl_cmd)
rc, out, err = run_helm(module, tmpl_cmd)
else:
out = err = ""
rc = 0
module.exit_json(
failed=False, changed=True, command=tmpl_cmd, stdout=out, stderr=err, rc=rc
failed=False,
changed=True,
command=tmpl_cmd,
stdout=out,
stderr=err,
rc=rc
)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -10,7 +10,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
module: k8s
@@ -57,14 +57,15 @@ options:
- Whether to override the default patch merge approach with a specific type. By default, the strategic
merge will typically be used.
- For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may
want to use C(merge) if you see "strategic merge patch format is not supported".
want to use C(merge) if you see "strategic merge patch format is not supported"
- See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
- If more than one C(merge_type) is given, the merge_types will be tried in order. This defaults to
C(['strategic-merge', 'merge']), which is ideal for using the same parameters on resource kinds that
combine Custom Resources and built-in resources.
- Mutually exclusive with C(apply).
- I(merge_type=json) has been removed in version 4.0.0. Please use M(kubernetes.core.k8s_json_patch) instead.
- mutually exclusive with C(apply)
- I(merge_type=json) is deprecated and will be removed in version 3.0.0. Please use M(kubernetes.core.k8s_json_patch) instead.
choices:
- json
- merge
- strategic-merge
type: list
@@ -100,14 +101,13 @@ options:
- C(apply) compares the desired resource definition with the previously supplied resource definition,
ignoring properties that are automatically generated
- C(apply) works better with Services than 'force=yes'
- Mutually exclusive with C(merge_type).
- mutually exclusive with C(merge_type)
default: False
type: bool
template:
description:
- Provide a valid YAML template definition file for an object when creating or updating.
- 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).
- Template files needs to be present on the Ansible Controller's file system.
- Additional parameters can be specified using dictionary.
@@ -135,72 +135,15 @@ options:
type: bool
default: False
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
delete_all:
description:
- When this option is set to I(true) and I(state=absent),
module will delete all resources of the specified resource type in the requested namespace.
- Ignored when C(state) is not set to I(absent) or when one of (src),
C(name) or C(resource_definition) is provided.
- Parameter C(kind) is required to use this option.
- This parameter can be used with C(label_selectors) to restrict the resources to be deleted.
type: bool
default: false
version_added: 2.5.0
aliases:
- all
hidden_fields:
description:
- Hide fields matching this option in the result
- An example might be C(hidden_fields=[metadata.managedFields])
- Only field definitions that don't reference list items are supported (so V(spec.containers[0]) would not work)
type: list
elements: str
version_added: 2.5.0
requirements:
- "python >= 3.9"
- "kubernetes >= 24.2.0"
- "python >= 3.6"
- "kubernetes >= 12.0.0"
- "PyYAML >= 3.11"
- "jsonpatch"
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Create a k8s namespace
kubernetes.core.k8s:
name: testing
@@ -252,14 +195,6 @@ EXAMPLES = r"""
state: present
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
- name: >-
(Alternative) Read definition file from the Ansible controller file system.
In this case, the definition file contains multiple YAML documents, separated by ---.
If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
kubernetes.core.k8s:
state: present
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml_all }}"
- name: Read definition template file from the Ansible controller file system
kubernetes.core.k8s:
state: present
@@ -273,15 +208,6 @@ EXAMPLES = r"""
variable_start_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
kubernetes.core.k8s:
state: present
@@ -336,44 +262,9 @@ EXAMPLES = r"""
metadata:
labels:
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
# Delete all Deployment from specified namespace
- name: Delete all Deployment from specified namespace
kubernetes.core.k8s:
api_version: apps/v1
namespace: testing
kind: Deployment
delete_all: true
"""
RETURN = r"""
RETURN = r'''
result:
description:
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
@@ -413,43 +304,20 @@ result:
description: error while trying to create/delete the object.
returned: error
type: complex
"""
'''
import copy
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
DELETE_OPTS_ARG_SPEC,
NAME_ARG_SPEC,
RESOURCE_ARG_SPEC,
WAIT_ARG_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
AnsibleK8SModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
run_module,
)
AUTH_ARG_SPEC, WAIT_ARG_SPEC, NAME_ARG_SPEC, RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC)
def validate_spec():
return dict(
fail_on_error=dict(type="bool"),
fail_on_error=dict(type='bool'),
version=dict(),
strict=dict(type="bool", default=True),
)
def server_apply_spec():
return dict(
field_manager=dict(type="str", required=True),
force_conflicts=dict(type="bool", default=False),
strict=dict(type='bool', default=True)
)
@@ -458,52 +326,55 @@ def argspec():
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
argument_spec["merge_type"] = dict(
type="list", elements="str", choices=["merge", "strategic-merge"]
)
argument_spec["validate"] = dict(type="dict", default=None, options=validate_spec())
argument_spec["append_hash"] = dict(type="bool", default=False)
argument_spec["apply"] = dict(type="bool", default=False)
argument_spec["template"] = dict(type="raw", default=None)
argument_spec["delete_options"] = dict(
type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)
)
argument_spec["continue_on_error"] = dict(type="bool", default=False)
argument_spec["state"] = dict(
default="present", choices=["present", "absent", "patched"]
)
argument_spec["force"] = dict(type="bool", default=False)
argument_spec["label_selectors"] = dict(type="list", elements="str")
argument_spec["generate_name"] = dict()
argument_spec["server_side_apply"] = dict(
type="dict", default=None, options=server_apply_spec()
)
argument_spec["delete_all"] = dict(type="bool", default=False, aliases=["all"])
argument_spec["hidden_fields"] = dict(type="list", elements="str")
argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
argument_spec['validate'] = dict(type='dict', default=None, options=validate_spec())
argument_spec['append_hash'] = dict(type='bool', default=False)
argument_spec['apply'] = dict(type='bool', default=False)
argument_spec['template'] = dict(type='raw', default=None)
argument_spec['delete_options'] = dict(type='dict', default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC))
argument_spec['continue_on_error'] = dict(type='bool', default=False)
argument_spec['state'] = dict(default='present', choices=['present', 'absent', 'patched'])
argument_spec['force'] = dict(type='bool', default=False)
return argument_spec
def execute_module(module, k8s_ansible_mixin):
k8s_ansible_mixin.module = module
k8s_ansible_mixin.argspec = module.argument_spec
k8s_ansible_mixin.check_mode = k8s_ansible_mixin.module.check_mode
k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
k8s_ansible_mixin.exit_json = k8s_ansible_mixin.module.exit_json
k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn
k8s_ansible_mixin.warnings = []
k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get('kind')
k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get('api_version')
k8s_ansible_mixin.name = k8s_ansible_mixin.params.get('name')
k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get('namespace')
k8s_ansible_mixin.check_library_version()
k8s_ansible_mixin.set_resource_definitions(module)
k8s_ansible_mixin.execute_module()
def main():
mutually_exclusive = [
("resource_definition", "src"),
("merge_type", "apply"),
("template", "resource_definition"),
("template", "src"),
("name", "generate_name"),
('resource_definition', 'src'),
('merge_type', 'apply'),
('template', 'resource_definition'),
('template', 'src'),
]
module = AnsibleModule(argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True)
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
K8sAnsibleMixin, get_api_client)
module = AnsibleK8SModule(
module_class=AnsibleModule,
argument_spec=argspec(),
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
)
try:
run_module(module)
except CoreException as e:
module.fail_from_exception(e)
k8s_ansible_mixin = K8sAnsibleMixin(module)
k8s_ansible_mixin.client = get_api_client(module=module)
execute_module(module, k8s_ansible_mixin)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -4,14 +4,13 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
DOCUMENTATION = r'''
module: k8s_cluster_info
version_added: 0.11.1
version_added: "0.11.1"
short_description: Describe Kubernetes (K8s) cluster, APIs available and their respective versions
@@ -34,12 +33,12 @@ extends_documentation_fragment:
- kubernetes.core.k8s_auth_options
requirements:
- "python >= 3.9"
- "kubernetes >= 24.2.0"
- "python >= 3.6"
- "kubernetes >= 12.0.0"
- "PyYAML >= 3.11"
"""
'''
EXAMPLES = r"""
EXAMPLES = r'''
- name: Get Cluster information
kubernetes.core.k8s_cluster_info:
register: api_status
@@ -48,9 +47,9 @@ EXAMPLES = r"""
kubernetes.core.k8s_cluster_info:
invalidate_cache: False
register: api_status
"""
'''
RETURN = r"""
RETURN = r'''
connection:
description:
- Connection information
@@ -137,96 +136,78 @@ apis:
description: Resource singular name
returned: success
type: str
"""
'''
import copy
import traceback
from collections import defaultdict
HAS_K8S = False
try:
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
ResourceList,
)
except ImportError:
# Handled during module setup
pass
from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ResourceList
HAS_K8S = True
except ImportError as e:
K8S_IMP_ERR = e
K8S_IMP_EXC = traceback.format_exc()
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
AnsibleK8SModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.parsing.convert_bool import boolean
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (AUTH_ARG_SPEC)
def execute_module(module, client):
if module.params.get("invalidate_cache"):
invalidate_cache = boolean(module.params.get('invalidate_cache', True), strict=False)
if invalidate_cache:
client.resources.invalidate_cache()
results = defaultdict(dict)
for resource in list(client.resources):
resource = resource[0]
if isinstance(resource, ResourceList):
continue
key = (
resource.group_version
if resource.group == ""
else "/".join([resource.group, resource.group_version.split("/")[-1]])
)
key = resource.group_version if resource.group == '' else '/'.join([resource.group, resource.group_version.split('/')[-1]])
results[key][resource.kind] = {
"categories": resource.categories if resource.categories else [],
"name": resource.name,
"namespaced": resource.namespaced,
"preferred": resource.preferred,
"short_names": resource.short_names if resource.short_names else [],
"singular_name": resource.singular_name,
'categories': resource.categories if resource.categories else [],
'name': resource.name,
'namespaced': resource.namespaced,
'preferred': resource.preferred,
'short_names': resource.short_names if resource.short_names else [],
'singular_name': resource.singular_name,
}
configuration = client.configuration
connection = {
"cert_file": configuration.cert_file,
"host": configuration.host,
"password": configuration.password,
"proxy": configuration.proxy,
"ssl_ca_cert": configuration.ssl_ca_cert,
"username": configuration.username,
"verify_ssl": configuration.verify_ssl,
'cert_file': configuration.cert_file,
'host': configuration.host,
'password': configuration.password,
'proxy': configuration.proxy,
'ssl_ca_cert': configuration.ssl_ca_cert,
'username': configuration.username,
'verify_ssl': configuration.verify_ssl,
}
from kubernetes import __version__ as version
version_info = {
"client": version,
"server": client.client.version,
'client': version,
'server': client.version,
}
module.exit_json(
changed=False, apis=results, connection=connection, version=version_info
)
module.exit_json(changed=False, apis=results, connection=connection, version=version_info)
def argspec():
spec = copy.deepcopy(AUTH_ARG_SPEC)
spec["invalidate_cache"] = dict(type="bool", default=True)
spec['invalidate_cache'] = dict(type='bool', default=True)
return spec
def main():
module = AnsibleK8SModule(
module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client,
)
try:
execute_module(module, client=get_api_client(module=module))
except CoreException as e:
module.fail_from_exception(e)
module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True)
if not HAS_K8S:
module.fail_json(msg=missing_required_lib('kubernetes'), exception=K8S_IMP_EXC,
error=to_native(K8S_IMP_ERR))
from ansible_collections.kubernetes.core.plugins.module_utils.common import get_api_client
execute_module(module, client=get_api_client(module=module))
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,224 +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.9"
- "kubernetes >= 24.2.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 content into a file in the remote pod
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.module_utils._text import to_native
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
AnsibleModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
AUTH_ARG_SPEC,
)
from ansible_collections.kubernetes.core.plugins.module_utils.copy import (
K8SCopyFromPod,
K8SCopyToPod,
check_pod,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
AnsibleK8SModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
CoreException,
)
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
K8sService,
)
def 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):
client = get_api_client(module=module)
svc = K8sService(client, module)
containers = check_pod(svc)
if len(containers) > 1 and module.params.get("container") is None:
module.fail_json(
msg="Pod contains more than 1 container, option 'container' should be set"
)
state = module.params.get("state")
if state == "to_pod":
k8s_copy = K8SCopyToPod(module, client.client)
else:
k8s_copy = K8SCopyFromPod(module, client.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 = AnsibleK8SModule(
module_class=AnsibleModule,
argument_spec=argspec(),
check_pyyaml=False,
mutually_exclusive=[("local_path", "content")],
required_if=[("state", "from_pod", ["local_path"])],
required_one_of=[["local_path", "content"]],
supports_check_mode=True,
)
try:
execute_module(module)
except CoreException as e:
module.fail_from_exception(e)
if __name__ == "__main__":
main()

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