mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-11 20:12:18 +00:00
Compare commits
69 Commits
2.0.1
...
stable-2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0de560df27 | ||
|
|
243bc00599 | ||
|
|
7e00e1ef87 | ||
|
|
1b412e06ab | ||
|
|
11c800d6ed | ||
|
|
0d9c4d3459 | ||
|
|
3645c1c16c | ||
|
|
5fdd70cbc3 | ||
|
|
1c02fe3443 | ||
|
|
eaffde63bb | ||
|
|
d8538ffed3 | ||
|
|
bc168a5727 | ||
|
|
dc5a1e6dd1 | ||
|
|
72536fe286 | ||
|
|
f2d899b939 | ||
|
|
bc391218a4 | ||
|
|
83b3a1aa39 | ||
|
|
aa41055503 | ||
|
|
256fa58ca8 | ||
|
|
70db517265 | ||
|
|
a27c701afe | ||
|
|
a48a68ecfd | ||
|
|
db11675622 | ||
|
|
07ac24e42e | ||
|
|
63b84d7f54 | ||
|
|
b397439972 | ||
|
|
8bb455afb9 | ||
|
|
ddbc161121 | ||
|
|
1da4ef1d53 | ||
|
|
d78b64d792 | ||
|
|
e21ad0212d | ||
|
|
47d149f774 | ||
|
|
77775f25a7 | ||
|
|
688cff4ea8 | ||
|
|
a4701a6806 | ||
|
|
b875531c8a | ||
|
|
76a77aff1f | ||
|
|
25590804cb | ||
|
|
7fbfc985ab | ||
|
|
4b682666f1 | ||
|
|
abd2abb33e | ||
|
|
f5a81941ff | ||
|
|
fa819d3f11 | ||
|
|
f98469e5ef | ||
|
|
c330c7ec65 | ||
|
|
2f59c3db77 | ||
|
|
2076da7dc0 | ||
|
|
3c36b6fa0f | ||
|
|
e9be88f212 | ||
|
|
a0a6d7121f | ||
|
|
2e98493010 | ||
|
|
5fb3ecbb50 | ||
|
|
eab3aa29bf | ||
|
|
04f227e9b9 | ||
|
|
25100e7f5e | ||
|
|
ccc2b61719 | ||
|
|
8c7b302916 | ||
|
|
abcc3e884c | ||
|
|
15799b2dd5 | ||
|
|
35af8a48ad | ||
|
|
8280bb78c0 | ||
|
|
2eca446f09 | ||
|
|
cd72b6d7df | ||
|
|
b50f1f2fc9 | ||
|
|
2594ac654b | ||
|
|
c11a255026 | ||
|
|
46494a18bd | ||
|
|
4ccb15d4ad | ||
|
|
58dba6bf22 |
140
.github/workflows/ci.yml
vendored
140
.github/workflows/ci.yml
vendored
@@ -1,140 +0,0 @@
|
||||
---
|
||||
name: CI
|
||||
'on':
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
jobs:
|
||||
|
||||
sanity:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: ['3.7']
|
||||
ansible_version: ['stable-2.11', 'stable-2.10', 'stable-2.9', 'devel']
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/kubernetes/core
|
||||
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
- name: Check ansible version
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ansible/ansible
|
||||
ref: ${{ matrix.ansible_version }}
|
||||
path: ansible_collections/kubernetes/core/ansible
|
||||
|
||||
- name: Run sanity tests on Python ${{ matrix.python_version }}
|
||||
run: source ./ansible/hacking/env-setup && make test-sanity PYTHON_VERSION=${{ matrix.python_version }}
|
||||
working-directory: ./ansible_collections/kubernetes/core
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# Our old integration tests fail under newer Python versions.
|
||||
python_version: ['3.6']
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/kubernetes/core
|
||||
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
- name: Install ansible base (devel branch)
|
||||
run: pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Run integration tests on Python ${{ matrix.python_version }}
|
||||
run: make test-integration PYTHON_VERSION=${{ matrix.python_version }}
|
||||
working-directory: ./ansible_collections/kubernetes/core
|
||||
|
||||
- name: Generate coverage report.
|
||||
run: ansible-test coverage xml -v --requirements --group-by command --group-by version
|
||||
working-directory: ./ansible_collections/kubernetes/core
|
||||
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
|
||||
molecule:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: ['3.7']
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/kubernetes/core
|
||||
|
||||
- name: Set up KinD cluster
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
# The 3.3.0 release of molecule introduced a breaking change. See
|
||||
# https://github.com/ansible-community/molecule/issues/3083
|
||||
- name: Install molecule and kubernetes dependencies
|
||||
run: pip install ansible "molecule<3.3.0" yamllint kubernetes flake8 jsonpatch
|
||||
|
||||
# The latest release doesn't work with Molecule currently.
|
||||
# See: https://github.com/ansible-community/molecule/issues/2757
|
||||
# - name: Install ansible base, latest release.
|
||||
# run: |
|
||||
# pip uninstall -y ansible
|
||||
# pip install --pre ansible-base
|
||||
|
||||
# The devel branch doesn't work with Molecule currently.
|
||||
# See: https://github.com/ansible-community/molecule/issues/2757
|
||||
# - name: Install ansible base (devel branch)
|
||||
# run: |
|
||||
# pip uninstall -y ansible
|
||||
# pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||
- name: Create default collection path symlink
|
||||
run: |
|
||||
mkdir -p /home/runner/.ansible
|
||||
ln -s /home/runner/work/kubernetes/kubernetes /home/runner/.ansible/collections
|
||||
|
||||
- name: Run molecule default test scenario
|
||||
run: make test-molecule
|
||||
working-directory: ./ansible_collections/kubernetes/core
|
||||
|
||||
unit:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: ['3.7']
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ansible_collections/kubernetes/core
|
||||
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
- name: Install ansible base (devel branch)
|
||||
run: pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||
|
||||
- name: Run unit tests on Python ${{ matrix.python_version }}
|
||||
run: make test-unit PYTHON_VERSION=${{ matrix.python_version }}
|
||||
working-directory: ./ansible_collections/kubernetes/core
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -13,3 +13,10 @@ changelogs/.plugin-cache.yaml
|
||||
tests/output
|
||||
tests/integration/cloud-config-*
|
||||
.cache
|
||||
|
||||
# Helm charts
|
||||
tests/integration/*-chart-*.tgz
|
||||
|
||||
# ansible-test generated file
|
||||
tests/integration/inventory
|
||||
tests/integration/*-*.yml
|
||||
|
||||
@@ -16,3 +16,5 @@ rules:
|
||||
indent-sequences: consistent
|
||||
ignore: |
|
||||
.cache
|
||||
.tox
|
||||
tests/output
|
||||
|
||||
10
.zuul.d/network-ee-sanity-tests_non-voting.yaml
Normal file
10
.zuul.d/network-ee-sanity-tests_non-voting.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- project:
|
||||
name: github.com/ansible-collections/kubernetes.core
|
||||
check:
|
||||
jobs:
|
||||
- network-ee-sanity-tests:
|
||||
voting: false
|
||||
gate:
|
||||
jobs:
|
||||
- network-ee-sanity-tests:
|
||||
voting: false
|
||||
@@ -5,6 +5,104 @@ Kubernetes Collection Release Notes
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v2.2.3
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
||||
- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Add integration test to check handling of module_defaults (https://github.com/ansible-collections/kubernetes.core/pull/296).
|
||||
|
||||
v2.2.2
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
||||
|
||||
v2.2.1
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
||||
|
||||
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
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- check auth params for existence, not whether they are true (https://github.com/ansible-collections/kubernetes.core/pull/151).
|
||||
|
||||
v2.1.0
|
||||
======
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- remove cloud.common as default dependency (https://github.com/ansible-collections/kubernetes.core/pull/148).
|
||||
- temporarily disable turbo mode (https://github.com/ansible-collections/kubernetes.core/pull/149).
|
||||
|
||||
v2.0.2
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix apply for k8s module when an array attribute from definition contains empty dict (https://github.com/ansible-collections/kubernetes.core/issues/113).
|
||||
- rename the apply function to fix broken imports in Ansible 2.9 (https://github.com/ansible-collections/kubernetes.core/pull/135).
|
||||
|
||||
v2.0.1
|
||||
======
|
||||
|
||||
|
||||
9
Makefile
9
Makefile
@@ -1,8 +1,8 @@
|
||||
# Also needs to be updated in galaxy.yml
|
||||
VERSION = 2.0.1
|
||||
VERSION = 2.2.3
|
||||
|
||||
TEST_ARGS ?= ""
|
||||
PYTHON_VERSION ?= `python -c 'import platform; print("{0}.{1}".format(platform.python_version_tuple()[0], platform.python_version_tuple()[1]))'`
|
||||
PYTHON_VERSION ?= `python -c 'import platform; print(".".join(platform.python_version_tuple()[0:2]))'`
|
||||
|
||||
clean:
|
||||
rm -f kubernetes-core-${VERSION}.tar.gz
|
||||
@@ -22,10 +22,7 @@ test-sanity:
|
||||
ansible-test sanity --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
||||
|
||||
test-integration:
|
||||
ansible-test integration --docker -v --color --retry-on-error --python $(PYTHON_VERSION) --continue-on-error --diff --coverage $(?TEST_ARGS)
|
||||
|
||||
test-molecule:
|
||||
molecule test
|
||||
ansible-test integration --diff --no-temp-workdir --color --skip-tags False --retry-on-error --continue-on-error --python $(PYTHON_VERSION) -v --coverage $(?TEST_ARGS)
|
||||
|
||||
test-unit:
|
||||
ansible-test units --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
|
||||
|
||||
48
PSF-license.txt
Normal file
48
PSF-license.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
|
||||
All Rights Reserved" are retained in Python alone or in any derivative version
|
||||
prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
22
README.md
22
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/ansible-collections/kubernetes.core/actions) [](https://codecov.io/gh/ansible-collections/kubernetes.core)
|
||||
|
||||
This repo hosts the `kubernetes.core` (formerly known as `community.kubernetes`) Ansible Collection.
|
||||
This repository 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.
|
||||
|
||||
@@ -46,6 +46,7 @@ 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
|
||||
@@ -58,6 +59,8 @@ Name | Description
|
||||
[kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates
|
||||
[kubernetes.core.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
|
||||
@@ -82,7 +85,7 @@ You can also include it in a `requirements.yml` file and install it via `ansible
|
||||
---
|
||||
collections:
|
||||
- name: kubernetes.core
|
||||
version: 2.0.1
|
||||
version: 2.2.3
|
||||
```
|
||||
|
||||
### Installing the Kubernetes Python Library
|
||||
@@ -159,10 +162,21 @@ 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
|
||||
## Ansible Turbo mode Tech Preview
|
||||
|
||||
The ``kubernetes.core`` collection supports Ansible Turbo mode via ``cloud.common`` collection. Please read more about Ansible Turbo mode - [here](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/ansible_turbo_mode.rst).
|
||||
|
||||
The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- hosts: remote
|
||||
environment:
|
||||
ENABLE_TURBO_MODE: 1
|
||||
tasks:
|
||||
...
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -403,3 +403,118 @@ releases:
|
||||
fragments:
|
||||
- 128-update-inventory-plugin-param.yaml
|
||||
release_date: '2021-06-11'
|
||||
2.0.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Fix apply for k8s module when an array attribute from definition contains
|
||||
empty dict (https://github.com/ansible-collections/kubernetes.core/issues/113).
|
||||
- rename the apply function to fix broken imports in Ansible 2.9 (https://github.com/ansible-collections/kubernetes.core/pull/135).
|
||||
fragments:
|
||||
- 129-k8s-fix-apply-array-with-empty-dict.yml
|
||||
- 135-rename-apply-function.yml
|
||||
release_date: '2021-06-16'
|
||||
2.1.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- remove cloud.common as default dependency (https://github.com/ansible-collections/kubernetes.core/pull/148).
|
||||
- temporarily disable turbo mode (https://github.com/ansible-collections/kubernetes.core/pull/149).
|
||||
fragments:
|
||||
- 148-remove-cloud-common-dependency.yaml
|
||||
- 149-disable-turbo-mode.yaml
|
||||
release_date: '2021-06-23'
|
||||
2.1.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- check auth params for existence, not whether they are true (https://github.com/ansible-collections/kubernetes.core/pull/151).
|
||||
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.2.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- common - Ensure the label_selectors parameter of _wait_for method is optional.
|
||||
fragments:
|
||||
- 0-copy_ignore_txt.yml
|
||||
- _wait_for_label_selector_optional.yaml
|
||||
release_date: '2021-10-18'
|
||||
2.2.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
|
||||
fragments:
|
||||
- 298-remove-binary-file.yaml
|
||||
release_date: '2021-12-07'
|
||||
2.2.3:
|
||||
changes:
|
||||
bugfixes:
|
||||
- k8s_drain - fix error caused by accessing an undefined variable when pods
|
||||
have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
|
||||
- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
|
||||
- module_utils.common - change default opening mode to read-bytes to avoid bad
|
||||
interpretation of non ascii characters and strings, often present in 3rd party
|
||||
manifests.
|
||||
minor_changes:
|
||||
- Add integration test to check handling of module_defaults
|
||||
(https://github.com/ansible-collections/kubernetes.core/pull/296).
|
||||
fragments:
|
||||
- 253-dont-wait-on-list-resources.yaml
|
||||
- 295-fix-k8s-drain-variable-declaration.yaml
|
||||
- 308-fix-for-common-non-ascii-characters-in-resources.yaml
|
||||
release_date: '2022-01-18'
|
||||
|
||||
2
changelogs/fragments/456-replace-distutils.yml
Normal file
2
changelogs/fragments/456-replace-distutils.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
|
||||
2
changelogs/fragments/disutils.version.yml
Normal file
2
changelogs/fragments/disutils.version.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
bugfixes:
|
||||
- "Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314)."
|
||||
3
changelogs/fragments/exception.yml
Normal file
3
changelogs/fragments/exception.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
bugfixes:
|
||||
- import exception from ``kubernetes.client.rest``.
|
||||
@@ -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 simply installing the cloud.common 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.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
@@ -24,6 +24,15 @@ 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
|
||||
------------
|
||||
@@ -90,7 +99,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 ``sync`` keyword in your Ansible module.
|
||||
Consequently you can use the ``async`` keyword in your Ansible module.
|
||||
This will be handy if you interact with a lot of remote systems
|
||||
at the same time.
|
||||
|
||||
|
||||
5
docs/docsite/extra-docs.yml
Normal file
5
docs/docsite/extra-docs.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
sections:
|
||||
- title: Scenario Guide
|
||||
toctree:
|
||||
- scenario_guide
|
||||
51
docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst
Normal file
51
docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst
Normal file
@@ -0,0 +1,51 @@
|
||||
.. _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>`_.
|
||||
88
docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst
Normal file
88
docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst
Normal file
@@ -0,0 +1,88 @@
|
||||
.. _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
|
||||
12
docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst
Normal file
12
docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
.. _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
|
||||
175
docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst
Normal file
175
docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst
Normal file
@@ -0,0 +1,175 @@
|
||||
.. _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
|
||||
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
|
||||
18
docs/docsite/rst/scenario_guide.rst
Normal file
18
docs/docsite/rst/scenario_guide.rst
Normal file
@@ -0,0 +1,18 @@
|
||||
.. _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
|
||||
|
||||
@@ -231,6 +231,23 @@ Parameters
|
||||
<div>Helm option to force reinstall, ignore on new install.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>history_max</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">integer</span>
|
||||
</div>
|
||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Limit the maximum number of revisions saved per release.</div>
|
||||
<div>mutually exclusive with with <code>replace</code>.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
@@ -373,6 +390,7 @@ 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>
|
||||
|
||||
@@ -161,13 +161,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
554
docs/kubernetes.core.k8s_cp_module.rst
Normal file
554
docs/kubernetes.core.k8s_cp_module.rst
Normal file
@@ -0,0 +1,554 @@
|
||||
.. _kubernetes.core.k8s_cp_module:
|
||||
|
||||
|
||||
**********************
|
||||
kubernetes.core.k8s_cp
|
||||
**********************
|
||||
|
||||
**Copy files and directories to and from pod.**
|
||||
|
||||
|
||||
Version added: 2.2.0
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
- Use the Kubernetes Python client to copy files and directories to and from containers inside a pod.
|
||||
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
The below requirements are needed on the host that executes this module.
|
||||
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table border=0 cellpadding=0 class="documentation-table">
|
||||
<tr>
|
||||
<th colspan="2">Parameter</th>
|
||||
<th>Choices/<font color="blue">Defaults</font></th>
|
||||
<th width="100%">Comments</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>api_key</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>ca_cert</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>client_cert</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>client_key</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>container</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The name of the container in the pod to copy files/directories from/to.</div>
|
||||
<div>Defaults to the only container if there is only one container in the pod.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>content</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>When used instead of <em>local_path</em>, sets the contents of a local file directly to the specified value.</div>
|
||||
<div>Works only when <em>remote_path</em> is a file. Creates the file if it does not exist.</div>
|
||||
<div>For advanced formatting or if the content contains a variable, use the <span class='module'>ansible.builtin.template</span> module.</div>
|
||||
<div>Mutually exclusive with <em>local_path</em>.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>context</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>host</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>kubeconfig</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>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> ←</div></li>
|
||||
<li>yes</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<div>The copied file/directory'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>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> ←</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's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
.. note::
|
||||
- the tar binary is required on the container when copying from local filesystem to pod.
|
||||
- To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
|
||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/bar
|
||||
local_path: /tmp/foo
|
||||
|
||||
# kubectl cp /tmp/foo_dir some-namespace/some-pod:/tmp/bar_dir
|
||||
- name: Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/bar_dir
|
||||
local_path: /tmp/foo_dir
|
||||
|
||||
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar -c some-container
|
||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
container: some-container
|
||||
remote_path: /tmp/bar
|
||||
local_path: /tmp/foo
|
||||
no_preserve: True
|
||||
state: to_pod
|
||||
|
||||
# kubectl cp some-namespace/some-pod:/tmp/foo /tmp/bar
|
||||
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/foo
|
||||
local_path: /tmp/bar
|
||||
state: from_pod
|
||||
|
||||
# copy content into a file in the remote pod
|
||||
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||
kubernetes.core.k8s_cp:
|
||||
state: to_pod
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/foo.txt
|
||||
content: "This content will be copied into remote file"
|
||||
|
||||
|
||||
|
||||
Return Values
|
||||
-------------
|
||||
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table border=0 cellpadding=0 class="documentation-table">
|
||||
<tr>
|
||||
<th colspan="1">Key</th>
|
||||
<th>Returned</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>result</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>message describing the copy operation successfully done.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/><br/>
|
||||
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
- Aubin Bikouo (@abikouo)
|
||||
561
docs/kubernetes.core.k8s_drain_module.rst
Normal file
561
docs/kubernetes.core.k8s_drain_module.rst
Normal file
@@ -0,0 +1,561 @@
|
||||
.. _kubernetes.core.k8s_drain_module:
|
||||
|
||||
|
||||
*************************
|
||||
kubernetes.core.k8s_drain
|
||||
*************************
|
||||
|
||||
**Drain, Cordon, or Uncordon node in k8s cluster**
|
||||
|
||||
|
||||
Version added: 2.2.0
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
- Drain node in preparation for maintenance same as kubectl drain.
|
||||
- Cordon will mark the node as unschedulable.
|
||||
- Uncordon will mark the node as schedulable.
|
||||
- The given node will be marked unschedulable to prevent new pods from arriving.
|
||||
- Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
|
||||
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
The below requirements are needed on the host that executes this module.
|
||||
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table border=0 cellpadding=0 class="documentation-table">
|
||||
<tr>
|
||||
<th colspan="2">Parameter</th>
|
||||
<th>Choices/<font color="blue">Defaults</font></th>
|
||||
<th width="100%">Comments</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>api_key</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>ca_cert</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>client_cert</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>client_key</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">path</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>context</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>delete_options</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">dictionary</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Specify options to delete pods.</div>
|
||||
<div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"></td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>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> ←</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> ←</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> ←</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>kubeconfig</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>name</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
/ <span style="color: red">required</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The name of the node.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>password</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
|
||||
<div>Please read the description of the <code>username</code> option for a discussion of when this option is applicable.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>persist_config</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">boolean</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
||||
<li>no</li>
|
||||
<li>yes</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<div>Whether or not to save the kube config refresh tokens. Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.</div>
|
||||
<div>When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the new refresh token to the kube config file.</div>
|
||||
<div>Default to false.</div>
|
||||
<div>Please note that the current version of the k8s python client library does not support setting this flag to True yet.</div>
|
||||
<div>The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>proxy</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.</div>
|
||||
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>proxy_headers</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">dictionary</span>
|
||||
</div>
|
||||
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>The Header used for the HTTP proxy.</div>
|
||||
<div>Documentation can be found here <a href='https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers'>https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers</a>.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"></td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>basic_auth</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Colon-separated username:password for basic authentication header.</div>
|
||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"></td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>proxy_basic_auth</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Colon-separated username:password for proxy basic authentication header.</div>
|
||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"></td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>user_agent</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>String representing the user-agent you want, such as foo/1.0.</div>
|
||||
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>state</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<ul style="margin: 0; padding: 0"><b>Choices:</b>
|
||||
<li>cordon</li>
|
||||
<li><div style="color: blue"><b>drain</b> ←</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's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
|
||||
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
.. note::
|
||||
- To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: drain
|
||||
name: foo
|
||||
force: yes
|
||||
|
||||
- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: drain
|
||||
name: foo
|
||||
delete_options:
|
||||
terminate_grace_period: 900
|
||||
|
||||
- name: Mark node "foo" as schedulable.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: uncordon
|
||||
name: foo
|
||||
|
||||
- name: Mark node "foo" as unschedulable.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: cordon
|
||||
name: foo
|
||||
|
||||
|
||||
|
||||
Return Values
|
||||
-------------
|
||||
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table border=0 cellpadding=0 class="documentation-table">
|
||||
<tr>
|
||||
<th colspan="1">Key</th>
|
||||
<th>Returned</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>result</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>The node status and the number of pods deleted.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/><br/>
|
||||
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
- Aubin Bikouo (@abikouo)
|
||||
@@ -172,13 +172,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -382,6 +383,7 @@ 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.
|
||||
@@ -410,7 +412,7 @@ Examples
|
||||
- name: Check last command status
|
||||
debug:
|
||||
msg: "cmd failed"
|
||||
when: command_status.return_code != 0
|
||||
when: command_status.rc != 0
|
||||
|
||||
|
||||
|
||||
@@ -443,6 +445,23 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </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"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>return_code</b>
|
||||
@@ -453,7 +472,7 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div>The command status code</div>
|
||||
<div>The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -198,13 +198,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -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:`k8s <k8s_module>` module for strategic merge or JSON merge operations.
|
||||
- Use the :ref:`kubernetes.core.k8s <kubernetes.core.k8s_module>` module for strategic merge or JSON merge operations.
|
||||
- The jsonpatch library is required for check mode.
|
||||
|
||||
|
||||
@@ -178,13 +178,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -198,13 +198,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -383,6 +384,22 @@ 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>
|
||||
@@ -457,6 +474,7 @@ 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
|
||||
|
||||
@@ -381,6 +381,12 @@ 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
|
||||
@@ -390,23 +396,23 @@ Examples
|
||||
|
||||
- name: Fetch a list of namespaces
|
||||
set_fact:
|
||||
projects: "{{ lookup('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
|
||||
projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
|
||||
|
||||
- name: Fetch all deployments
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
|
||||
|
||||
- name: Fetch all deployments in a namespace
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
|
||||
|
||||
- name: Fetch a specific deployment by name
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
|
||||
- name: Fetch with label selector
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
|
||||
# Use parameters from a YAML config
|
||||
|
||||
@@ -416,11 +422,11 @@ Examples
|
||||
|
||||
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', resource_definition=config) }}"
|
||||
service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}"
|
||||
|
||||
- name: Use a config from the local filesystem
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', src='service.yml') }}"
|
||||
service: "{{ query('kubernetes.core.k8s', src='service.yml') }}"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -375,13 +375,31 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="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>
|
||||
@@ -639,6 +657,7 @@ 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's file system.</div>
|
||||
<div>Additional parameters can be specified using dictionary.</div>
|
||||
@@ -975,6 +994,15 @@ 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
|
||||
|
||||
@@ -196,13 +196,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -213,13 +213,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -178,13 +178,14 @@ Parameters
|
||||
<b>kubeconfig</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: purple">raw</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
|
||||
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
251
docs/kubernetes.core.kustomize_lookup.rst
Normal file
251
docs/kubernetes.core.kustomize_lookup.rst
Normal file
@@ -0,0 +1,251 @@
|
||||
.. _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 'kustomization.yaml', or a git repository URL with a path suffix specifying same with respect to the repository root.</div>
|
||||
<div>If omitted, '.' is assumed.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="parameter-"></div>
|
||||
<b>opt_dirs</b>
|
||||
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">-</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div>An optional list of directories to search for the executable in addition to PATH.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
.. note::
|
||||
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- name: Run lookup using kustomize
|
||||
set_fact:
|
||||
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
|
||||
|
||||
- name: Run lookup using kubectl kustomize
|
||||
set_fact:
|
||||
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
|
||||
|
||||
- name: Create kubernetes resources for lookup output
|
||||
k8s:
|
||||
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
|
||||
|
||||
|
||||
|
||||
Return Values
|
||||
-------------
|
||||
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this lookup:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table border=0 cellpadding=0 class="documentation-table">
|
||||
<tr>
|
||||
<th colspan="2">Key</th>
|
||||
<th>Returned</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<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">complex</span>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div>One ore more object definitions returned from the tool execution.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>api_version</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>The versioned schema of this representation of an object.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>kind</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">string</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>Represents the REST resource this object represents.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>metadata</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">complex</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>Standard object metadata. Includes name, namespace, annotations, labels, etc.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>spec</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">complex</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>Specific attributes of the object. Will vary based on the <em>api_version</em> and <em>kind</em>.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="elbow-placeholder"> </td>
|
||||
<td colspan="1">
|
||||
<div class="ansibleOptionAnchor" id="return-"></div>
|
||||
<b>status</b>
|
||||
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
|
||||
<div style="font-size: small">
|
||||
<span style="color: purple">complex</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>success</td>
|
||||
<td>
|
||||
<div>Current status details for the object.</div>
|
||||
<br/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<br/><br/>
|
||||
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
- 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.
|
||||
@@ -8,8 +8,6 @@ authors:
|
||||
- willthames (https://github.com/willthames)
|
||||
- mmazur (https://github.com/mmazur)
|
||||
- jamescassell (https://github.com/jamescassell)
|
||||
dependencies:
|
||||
cloud.common: '>=2.0.1'
|
||||
description: Kubernetes Collection for Ansible.
|
||||
documentation: ''
|
||||
homepage: ''
|
||||
@@ -27,7 +25,7 @@ tags:
|
||||
- openshift
|
||||
- okd
|
||||
- cluster
|
||||
version: 2.0.1
|
||||
version: 2.2.3
|
||||
build_ignore:
|
||||
- .DS_Store
|
||||
- '*.tar.gz'
|
||||
|
||||
@@ -9,11 +9,12 @@ action_groups:
|
||||
k8s:
|
||||
- k8s
|
||||
- k8s_exec
|
||||
- k8s_facts
|
||||
- k8s_info
|
||||
- k8s_log
|
||||
- k8s_scale
|
||||
- k8s_service
|
||||
- k8s_cp
|
||||
- k8s_drain
|
||||
|
||||
plugin_routing:
|
||||
action:
|
||||
@@ -31,6 +32,10 @@ plugin_routing:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
k8s_cluster_info:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
k8s_cp:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
k8s_drain:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
k8s_event_info:
|
||||
redirect: kubernetes.core.k8s_info
|
||||
k8s_exec:
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: localhost
|
||||
connection: local
|
||||
|
||||
collections:
|
||||
- kubernetes.core
|
||||
|
||||
vars_files:
|
||||
- vars/main.yml
|
||||
|
||||
tasks:
|
||||
- name: Verify cluster is working.
|
||||
k8s_info:
|
||||
namespace: kube-system
|
||||
kind: Pod
|
||||
register: pod_list
|
||||
|
||||
- name: Verify cluster has more than 5 pods running.
|
||||
assert:
|
||||
that: (pod_list.resources | count) > 5
|
||||
|
||||
- name: Include access_review.yml
|
||||
include_tasks:
|
||||
file: tasks/access_review.yml
|
||||
apply:
|
||||
tags: [ access_review, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include append_hash.yml
|
||||
include_tasks:
|
||||
file: tasks/append_hash.yml
|
||||
apply:
|
||||
tags: [ append_hash, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include apply.yml
|
||||
include_tasks:
|
||||
file: tasks/apply.yml
|
||||
apply:
|
||||
tags: [ apply, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include cluster_info.yml
|
||||
include_tasks:
|
||||
file: tasks/cluster_info.yml
|
||||
apply:
|
||||
tags: [ cluster_info, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include crd.yml
|
||||
include_tasks:
|
||||
file: tasks/crd.yml
|
||||
apply:
|
||||
tags: [ crd, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include delete.yml
|
||||
include_tasks:
|
||||
file: tasks/delete.yml
|
||||
apply:
|
||||
tags: [ delete, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include exec.yml
|
||||
include_tasks:
|
||||
file: tasks/exec.yml
|
||||
apply:
|
||||
tags: [ exec, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include full.yml
|
||||
include_tasks:
|
||||
file: tasks/full.yml
|
||||
apply:
|
||||
tags: [ full, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include gc.yml
|
||||
include_tasks:
|
||||
file: tasks/gc.yml
|
||||
apply:
|
||||
tags: [ gc, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include info.yml
|
||||
include_tasks:
|
||||
file: tasks/info.yml
|
||||
apply:
|
||||
tags: [ info, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include json_patch.yml
|
||||
include_tasks:
|
||||
file: tasks/json_patch.yml
|
||||
apply:
|
||||
tags: [ json_patch, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include lists.yml
|
||||
include_tasks:
|
||||
file: tasks/lists.yml
|
||||
apply:
|
||||
tags: [ lists, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include log.yml
|
||||
include_tasks:
|
||||
file: tasks/log.yml
|
||||
apply:
|
||||
tags: [ log, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include rollback.yml
|
||||
include_tasks:
|
||||
file: tasks/rollback.yml
|
||||
apply:
|
||||
tags: [ rollback, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include scale.yml
|
||||
include_tasks:
|
||||
file: tasks/scale.yml
|
||||
apply:
|
||||
tags: [ scale, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include template.yml
|
||||
include_tasks:
|
||||
file: tasks/template.yml
|
||||
apply:
|
||||
tags: [ template, k8s ]
|
||||
tags:
|
||||
- always
|
||||
- name: Include waiter.yml
|
||||
include_tasks:
|
||||
file: tasks/waiter.yml
|
||||
apply:
|
||||
tags: [ waiter, k8s ]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Include merge_type.yml
|
||||
include_tasks:
|
||||
file: tasks/merge_type.yml
|
||||
apply:
|
||||
tags: [ merge_type, k8s ]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Include patched.yml
|
||||
include_tasks:
|
||||
file: tasks/patched.yml
|
||||
apply:
|
||||
tags: [ patched, k8s ]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Include lookup_k8s.yml
|
||||
include_tasks:
|
||||
file: tasks/lookup_k8s.yml
|
||||
apply:
|
||||
tags: [ lookup, k8s ]
|
||||
tags:
|
||||
- always
|
||||
|
||||
roles:
|
||||
- role: helm
|
||||
tags:
|
||||
- helm
|
||||
|
||||
post_tasks:
|
||||
- name: Ensure namespace exists
|
||||
k8s:
|
||||
api_version: v1
|
||||
kind: Namespace
|
||||
name: inventory
|
||||
|
||||
- name: Add a deployment
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: inventory
|
||||
namespace: inventory
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: "{{ k8s_pod_name }}"
|
||||
template: "{{ k8s_pod_template }}"
|
||||
wait: yes
|
||||
wait_timeout: 120
|
||||
vars:
|
||||
k8s_pod_name: inventory
|
||||
k8s_pod_image: python
|
||||
k8s_pod_command:
|
||||
- python
|
||||
- '-m'
|
||||
- http.server
|
||||
k8s_pod_env:
|
||||
- name: TEST
|
||||
value: test
|
||||
|
||||
- meta: refresh_inventory
|
||||
|
||||
- name: Verify inventory and connection plugins
|
||||
hosts: namespace_inventory_pods
|
||||
gather_facts: no
|
||||
|
||||
vars:
|
||||
file_content: |
|
||||
Hello world
|
||||
|
||||
tasks:
|
||||
- name: End play if host not running (TODO should we not add these to the inventory?)
|
||||
meta: end_host
|
||||
when: pod_phase != "Running"
|
||||
|
||||
- debug: var=hostvars
|
||||
- setup:
|
||||
|
||||
- debug: var=ansible_facts
|
||||
|
||||
- name: Assert the TEST environment variable was retrieved
|
||||
assert:
|
||||
that: ansible_facts.env.TEST == 'test'
|
||||
|
||||
- name: Copy a file into the host
|
||||
copy:
|
||||
content: '{{ file_content }}'
|
||||
dest: /tmp/test_file
|
||||
|
||||
- name: Retrieve the file from the host
|
||||
slurp:
|
||||
src: /tmp/test_file
|
||||
register: slurped_file
|
||||
|
||||
- name: Assert the file content matches expectations
|
||||
assert:
|
||||
that: (slurped_file.content|b64decode) == file_content
|
||||
|
||||
- name: Delete inventory namespace
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Remove inventory namespace
|
||||
k8s:
|
||||
api_version: v1
|
||||
kind: Namespace
|
||||
name: inventory
|
||||
state: absent
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
driver:
|
||||
name: delegated
|
||||
options:
|
||||
managed: false
|
||||
login_cmd_template: 'docker exec -ti {instance} bash'
|
||||
ansible_connection_options:
|
||||
ansible_connection: docker
|
||||
lint: |
|
||||
set -e
|
||||
yamllint .
|
||||
flake8
|
||||
platforms:
|
||||
- name: instance-kind
|
||||
provisioner:
|
||||
name: ansible
|
||||
log: true
|
||||
config_options:
|
||||
inventory:
|
||||
enable_plugins: kubernetes.core.k8s
|
||||
lint: {}
|
||||
inventory:
|
||||
hosts:
|
||||
plugin: kubernetes.core.k8s
|
||||
host_vars:
|
||||
localhost:
|
||||
ansible_python_interpreter: '{{ ansible_playbook_python }}'
|
||||
env:
|
||||
ANSIBLE_FORCE_COLOR: 'true'
|
||||
options:
|
||||
vvv: True
|
||||
scenario:
|
||||
name: default
|
||||
test_sequence:
|
||||
- dependency
|
||||
- lint
|
||||
- syntax
|
||||
- converge
|
||||
- verify
|
||||
dependency:
|
||||
name: galaxy
|
||||
options:
|
||||
requirements-file: requirements.yml
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
- block:
|
||||
# https://github.com/ansible-collections/kubernetes.core/issues/9
|
||||
- name: Create a namespace with label
|
||||
kubernetes.core.k8s:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "app-development-one"
|
||||
labels:
|
||||
namespace_label: "app_development"
|
||||
|
||||
- set_fact:
|
||||
namespace_info: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
|
||||
|
||||
- name: Check if the returned value is list with a single element
|
||||
assert:
|
||||
that:
|
||||
- namespace_info is iterable
|
||||
- not namespace_info is string
|
||||
- not namespace_info is mapping
|
||||
- namespace_info | length == 1
|
||||
|
||||
- name: Create another namespace with label
|
||||
kubernetes.core.k8s:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "app-development-two"
|
||||
labels:
|
||||
namespace_label: "app_development"
|
||||
|
||||
- set_fact:
|
||||
namespace_info: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
|
||||
|
||||
- name: Check if the returned value is list with 2 elements
|
||||
assert:
|
||||
that:
|
||||
- namespace_info is iterable
|
||||
- not namespace_info is string
|
||||
- not namespace_info is mapping
|
||||
- namespace_info | length == 2
|
||||
|
||||
always:
|
||||
- name: Ensure that namespace is removed
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- app-development-one
|
||||
- app-development-two
|
||||
1
plugins/action/helm.py
Symbolic link
1
plugins/action/helm.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_info.py
Symbolic link
1
plugins/action/helm_info.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_plugin.py
Symbolic link
1
plugins/action/helm_plugin.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_plugin_info.py
Symbolic link
1
plugins/action/helm_plugin_info.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_repository.py
Symbolic link
1
plugins/action/helm_repository.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s.py
Symbolic link
1
plugins/action/k8s.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_cluster_info.py
Symbolic link
1
plugins/action/k8s_cluster_info.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_cp.py
Symbolic link
1
plugins/action/k8s_cp.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_drain.py
Symbolic link
1
plugins/action/k8s_drain.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_exec.py
Symbolic link
1
plugins/action/k8s_exec.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
@@ -8,6 +8,7 @@ __metaclass__ = type
|
||||
|
||||
import copy
|
||||
import traceback
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@@ -113,7 +114,7 @@ class ActionModule(ActionBase):
|
||||
|
||||
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:
|
||||
@@ -126,8 +127,8 @@ class ActionModule(ActionBase):
|
||||
|
||||
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'):
|
||||
raise AnsibleActionFail("'template' is only supported parameter for 'k8s' module.")
|
||||
if self._task.action not in ('k8s', 'kubernetes.core.k8s', 'community.okd.k8s', 'redhat.openshift.k8s', 'community.kubernetes.k8s'):
|
||||
raise AnsibleActionFail("'template' is only a supported parameter for the 'k8s' module.")
|
||||
|
||||
template_params = []
|
||||
if isinstance(template, string_types) or isinstance(template, dict):
|
||||
@@ -179,6 +180,38 @@ class ActionModule(ActionBase):
|
||||
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
|
||||
kubeconfig = self._find_needle('files', kubeconfig)
|
||||
|
||||
# decrypt kubeconfig found
|
||||
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
||||
new_module_args['kubeconfig'] = actual_file
|
||||
|
||||
elif isinstance(kubeconfig, dict):
|
||||
new_module_args['kubeconfig'] = kubeconfig
|
||||
else:
|
||||
raise AnsibleActionFail("Error while reading kubeconfig parameter - "
|
||||
"a string or dict expected, but got %s instead" % type(kubeconfig))
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
''' handler for k8s options '''
|
||||
if task_vars is None:
|
||||
@@ -196,22 +229,15 @@ class ActionModule(ActionBase):
|
||||
new_module_args = copy.deepcopy(self._task.args)
|
||||
|
||||
kubeconfig = self._task.args.get('kubeconfig', None)
|
||||
# find the kubeconfig in the expected search path
|
||||
if kubeconfig and not remote_transport:
|
||||
# kubeconfig is local
|
||||
if kubeconfig:
|
||||
try:
|
||||
# find in expected paths
|
||||
kubeconfig = self._find_needle('files', kubeconfig)
|
||||
self.get_kubeconfig(kubeconfig, remote_transport, new_module_args)
|
||||
except AnsibleError as e:
|
||||
result['failed'] = True
|
||||
result['msg'] = to_text(e)
|
||||
result['exception'] = traceback.format_exc()
|
||||
return result
|
||||
|
||||
# decrypt kubeconfig found
|
||||
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
||||
new_module_args['kubeconfig'] = actual_file
|
||||
|
||||
# find the file in the expected search path
|
||||
src = self._task.args.get('src', None)
|
||||
|
||||
@@ -238,6 +264,11 @@ class ActionModule(ActionBase):
|
||||
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':
|
||||
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)
|
||||
|
||||
|
||||
1
plugins/action/k8s_log.py
Symbolic link
1
plugins/action/k8s_log.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_rollback.py
Symbolic link
1
plugins/action/k8s_rollback.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_scale.py
Symbolic link
1
plugins/action/k8s_scale.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_service.py
Symbolic link
1
plugins/action/k8s_service.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/ks8_json_patch.py
Symbolic link
1
plugins/action/ks8_json_patch.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
@@ -22,9 +22,9 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author:
|
||||
- xuxinkun
|
||||
- xuxinkun (@xuxinkun)
|
||||
|
||||
connection: kubectl
|
||||
name: kubectl
|
||||
|
||||
short_description: Execute tasks in pods running on Kubernetes.
|
||||
|
||||
@@ -170,9 +170,9 @@ DOCUMENTATION = r"""
|
||||
aliases: [ kubectl_verify_ssl ]
|
||||
"""
|
||||
|
||||
import distutils.spawn
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||
@@ -217,13 +217,10 @@ 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)
|
||||
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))
|
||||
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))
|
||||
|
||||
def _build_exec_cmd(self, cmd):
|
||||
""" Build the local kubectl exec command to run cmd on remote_host
|
||||
@@ -245,6 +242,8 @@ class Connection(ConnectionBase):
|
||||
# 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)]
|
||||
|
||||
extra_args_name = u'{0}_extra_args'.format(self.transport)
|
||||
if self.get_option(extra_args_name):
|
||||
|
||||
@@ -27,7 +27,8 @@ 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.
|
||||
type: path
|
||||
- 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
|
||||
context:
|
||||
description:
|
||||
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.
|
||||
|
||||
@@ -28,6 +28,6 @@ options:
|
||||
- Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
|
||||
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
|
||||
I(resource_definition). See Examples below.
|
||||
- Mutually exclusive with I(template) in case of M(k8s) module.
|
||||
- Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module.
|
||||
type: path
|
||||
'''
|
||||
|
||||
@@ -6,10 +6,9 @@ __metaclass__ = type
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -7,21 +7,24 @@ from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
lookup: k8s
|
||||
DOCUMENTATION = """
|
||||
name: 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,28 +117,28 @@ DOCUMENTATION = '''
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Fetch a list of namespaces
|
||||
set_fact:
|
||||
projects: "{{ lookup('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
|
||||
projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
|
||||
|
||||
- name: Fetch all deployments
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
|
||||
|
||||
- name: Fetch all deployments in a namespace
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
|
||||
|
||||
- name: Fetch a specific deployment by name
|
||||
set_fact:
|
||||
deployments: "{{ lookup('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
|
||||
- name: Fetch with label selector
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
|
||||
# Use parameters from a YAML config
|
||||
|
||||
@@ -145,39 +148,30 @@ EXAMPLES = """
|
||||
|
||||
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', resource_definition=config) }}"
|
||||
service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}"
|
||||
|
||||
- name: Use a config from the local filesystem
|
||||
set_fact:
|
||||
service: "{{ lookup('kubernetes.core.k8s', src='service.yml') }}"
|
||||
service: "{{ query('kubernetes.core.k8s', src='service.yml') }}"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
_list:
|
||||
description:
|
||||
- One ore more object definitions returned from the API.
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
@@ -264,7 +258,7 @@ class KubernetesLookup(K8sAnsibleMixin):
|
||||
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):
|
||||
|
||||
120
plugins/lookup/kustomize.py
Normal file
120
plugins/lookup/kustomize.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#
|
||||
# Copyright 2021 Red Hat | Ansible
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: kustomize
|
||||
|
||||
short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file.
|
||||
|
||||
version_added: "2.2.0"
|
||||
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
notes:
|
||||
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
|
||||
description:
|
||||
- Uses the kustomize or the kubectl tool.
|
||||
- Return the result of C(kustomize build) or C(kubectl kustomize).
|
||||
options:
|
||||
dir:
|
||||
description:
|
||||
- The directory path containing 'kustomization.yaml',
|
||||
or a git repository URL with a path suffix specifying same with respect to the repository root.
|
||||
- If omitted, '.' is assumed.
|
||||
default: "."
|
||||
binary_path:
|
||||
description:
|
||||
- The path of a kustomize or kubectl binary to use.
|
||||
opt_dirs:
|
||||
description:
|
||||
- An optional list of directories to search for the executable in addition to PATH.
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Run lookup using kustomize
|
||||
set_fact:
|
||||
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
|
||||
|
||||
- name: Run lookup using kubectl kustomize
|
||||
set_fact:
|
||||
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
|
||||
|
||||
- name: Create kubernetes resources for lookup output
|
||||
k8s:
|
||||
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
_list:
|
||||
description:
|
||||
- YAML string for the object definitions returned from the tool execution.
|
||||
type: str
|
||||
sample:
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-config-map
|
||||
namespace: default
|
||||
data:
|
||||
key1: val1
|
||||
"""
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_binary_from_path(name, opt_dirs=None):
|
||||
opt_arg = {}
|
||||
try:
|
||||
if opt_dirs is not None:
|
||||
if not isinstance(opt_dirs, list):
|
||||
opt_dirs = [opt_dirs]
|
||||
opt_arg['opt_dirs'] = opt_dirs
|
||||
bin_path = get_bin_path(name, **opt_arg)
|
||||
return bin_path
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def run_command(command):
|
||||
cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return cmd.communicate()
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
def run(self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs):
|
||||
executable_path = binary_path
|
||||
if executable_path is None:
|
||||
executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
|
||||
if executable_path is None:
|
||||
executable_path = get_binary_from_path(name="kubectl", opt_dirs=opt_dirs)
|
||||
|
||||
# validate that at least one tool was found
|
||||
if executable_path is None:
|
||||
raise AnsibleLookupError("Failed to find required executable 'kubectl' and 'kustomize' in paths")
|
||||
|
||||
# check input directory
|
||||
kustomization_dir = dir
|
||||
|
||||
command = [executable_path]
|
||||
if executable_path.endswith('kustomize'):
|
||||
command += ['build', kustomization_dir]
|
||||
elif executable_path.endswith('kubectl'):
|
||||
command += ['kustomize', kustomization_dir]
|
||||
else:
|
||||
raise AnsibleLookupError("unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(executable_path))
|
||||
|
||||
(out, err) = run_command(command)
|
||||
if err:
|
||||
raise AnsibleLookupError("kustomize command failed with: {0}".format(err.decode("utf-8")))
|
||||
return [out.decode('utf-8')]
|
||||
344
plugins/module_utils/_version.py
Normal file
344
plugins/module_utils/_version.py
Normal file
@@ -0,0 +1,344 @@
|
||||
# Vendored copy of distutils/version.py from CPython 3.9.5
|
||||
#
|
||||
# Implements multiple version numbering conventions for the
|
||||
# Python Module Distribution Utilities.
|
||||
#
|
||||
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||
#
|
||||
|
||||
"""Provides classes to represent module version numbers (one class for
|
||||
each style of version numbering). There are currently two such classes
|
||||
implemented: StrictVersion and LooseVersion.
|
||||
|
||||
Every version number class implements the following interface:
|
||||
* the 'parse' method takes a string and parses it to some internal
|
||||
representation; if the string is an invalid version number,
|
||||
'parse' raises a ValueError exception
|
||||
* the class constructor takes an optional string argument which,
|
||||
if supplied, is passed to 'parse'
|
||||
* __str__ reconstructs the string that was passed to 'parse' (or
|
||||
an equivalent string -- ie. one that will generate an equivalent
|
||||
version number instance)
|
||||
* __repr__ generates Python code to recreate the version number instance
|
||||
* _cmp compares the current instance with either another instance
|
||||
of the same class or a string (which will be parsed to an instance
|
||||
of the same class, thus must follow the same rules)
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
|
||||
try:
|
||||
RE_FLAGS = re.VERBOSE | re.ASCII
|
||||
except AttributeError:
|
||||
RE_FLAGS = re.VERBOSE
|
||||
|
||||
|
||||
class Version:
|
||||
"""Abstract base class for version numbering classes. Just provides
|
||||
constructor (__init__) and reproducer (__repr__), because those
|
||||
seem to be the same for all version numbering classes; and route
|
||||
rich comparisons to _cmp.
|
||||
"""
|
||||
|
||||
def __init__(self, vstring=None):
|
||||
if vstring:
|
||||
self.parse(vstring)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s ('%s')" % (self.__class__.__name__, str(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
c = self._cmp(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c == 0
|
||||
|
||||
def __lt__(self, other):
|
||||
c = self._cmp(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c < 0
|
||||
|
||||
def __le__(self, other):
|
||||
c = self._cmp(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
c = self._cmp(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
c = self._cmp(other)
|
||||
if c is NotImplemented:
|
||||
return c
|
||||
return c >= 0
|
||||
|
||||
|
||||
# Interface for version-number classes -- must be implemented
|
||||
# by the following classes (the concrete ones -- Version should
|
||||
# be treated as an abstract class).
|
||||
# __init__ (string) - create and take same action as 'parse'
|
||||
# (string parameter is optional)
|
||||
# parse (string) - convert a string representation to whatever
|
||||
# internal representation is appropriate for
|
||||
# this style of version numbering
|
||||
# __str__ (self) - convert back to a string; should be very similar
|
||||
# (if not identical to) the string supplied to parse
|
||||
# __repr__ (self) - generate Python code to recreate
|
||||
# the instance
|
||||
# _cmp (self, other) - compare two version numbers ('other' may
|
||||
# be an unparsed version string, or another
|
||||
# instance of your version class)
|
||||
|
||||
|
||||
class StrictVersion(Version):
|
||||
"""Version numbering for anal retentives and software idealists.
|
||||
Implements the standard interface for version number classes as
|
||||
described above. A version number consists of two or three
|
||||
dot-separated numeric components, with an optional "pre-release" tag
|
||||
on the end. The pre-release tag consists of the letter 'a' or 'b'
|
||||
followed by a number. If the numeric components of two version
|
||||
numbers are equal, then one with a pre-release tag will always
|
||||
be deemed earlier (lesser) than one without.
|
||||
|
||||
The following are valid version numbers (shown in the order that
|
||||
would be obtained by sorting according to the supplied cmp function):
|
||||
|
||||
0.4 0.4.0 (these two are equivalent)
|
||||
0.4.1
|
||||
0.5a1
|
||||
0.5b3
|
||||
0.5
|
||||
0.9.6
|
||||
1.0
|
||||
1.0.4a3
|
||||
1.0.4b1
|
||||
1.0.4
|
||||
|
||||
The following are examples of invalid version numbers:
|
||||
|
||||
1
|
||||
2.7.2.2
|
||||
1.3.a4
|
||||
1.3pl1
|
||||
1.3c4
|
||||
|
||||
The rationale for this version numbering system will be explained
|
||||
in the distutils documentation.
|
||||
"""
|
||||
|
||||
version_re = re.compile(r"^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$", RE_FLAGS)
|
||||
|
||||
def parse(self, vstring):
|
||||
match = self.version_re.match(vstring)
|
||||
if not match:
|
||||
raise ValueError("invalid version number '%s'" % vstring)
|
||||
|
||||
(major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
|
||||
|
||||
if patch:
|
||||
self.version = tuple(map(int, [major, minor, patch]))
|
||||
else:
|
||||
self.version = tuple(map(int, [major, minor])) + (0,)
|
||||
|
||||
if prerelease:
|
||||
self.prerelease = (prerelease[0], int(prerelease_num))
|
||||
else:
|
||||
self.prerelease = None
|
||||
|
||||
def __str__(self):
|
||||
if self.version[2] == 0:
|
||||
vstring = ".".join(map(str, self.version[0:2]))
|
||||
else:
|
||||
vstring = ".".join(map(str, self.version))
|
||||
|
||||
if self.prerelease:
|
||||
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
|
||||
|
||||
return vstring
|
||||
|
||||
def _cmp(self, other):
|
||||
if isinstance(other, str):
|
||||
other = StrictVersion(other)
|
||||
elif not isinstance(other, StrictVersion):
|
||||
return NotImplemented
|
||||
|
||||
if self.version != other.version:
|
||||
# numeric versions don't match
|
||||
# prerelease stuff doesn't matter
|
||||
if self.version < other.version:
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
|
||||
# have to compare prerelease
|
||||
# case 1: neither has prerelease; they're equal
|
||||
# case 2: self has prerelease, other doesn't; other is greater
|
||||
# case 3: self doesn't have prerelease, other does: self is greater
|
||||
# case 4: both have prerelease: must compare them!
|
||||
|
||||
if not self.prerelease and not other.prerelease:
|
||||
return 0
|
||||
elif self.prerelease and not other.prerelease:
|
||||
return -1
|
||||
elif not self.prerelease and other.prerelease:
|
||||
return 1
|
||||
elif self.prerelease and other.prerelease:
|
||||
if self.prerelease == other.prerelease:
|
||||
return 0
|
||||
elif self.prerelease < other.prerelease:
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
raise AssertionError("never get here")
|
||||
|
||||
|
||||
# end class StrictVersion
|
||||
|
||||
# The rules according to Greg Stein:
|
||||
# 1) a version number has 1 or more numbers separated by a period or by
|
||||
# sequences of letters. If only periods, then these are compared
|
||||
# left-to-right to determine an ordering.
|
||||
# 2) sequences of letters are part of the tuple for comparison and are
|
||||
# compared lexicographically
|
||||
# 3) recognize the numeric components may have leading zeroes
|
||||
#
|
||||
# The LooseVersion class below implements these rules: a version number
|
||||
# string is split up into a tuple of integer and string components, and
|
||||
# comparison is a simple tuple comparison. This means that version
|
||||
# numbers behave in a predictable and obvious way, but a way that might
|
||||
# not necessarily be how people *want* version numbers to behave. There
|
||||
# wouldn't be a problem if people could stick to purely numeric version
|
||||
# numbers: just split on period and compare the numbers as tuples.
|
||||
# However, people insist on putting letters into their version numbers;
|
||||
# the most common purpose seems to be:
|
||||
# - indicating a "pre-release" version
|
||||
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
|
||||
# - indicating a post-release patch ('p', 'pl', 'patch')
|
||||
# but of course this can't cover all version number schemes, and there's
|
||||
# no way to know what a programmer means without asking him.
|
||||
#
|
||||
# The problem is what to do with letters (and other non-numeric
|
||||
# characters) in a version number. The current implementation does the
|
||||
# obvious and predictable thing: keep them as strings and compare
|
||||
# lexically within a tuple comparison. This has the desired effect if
|
||||
# an appended letter sequence implies something "post-release":
|
||||
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
|
||||
#
|
||||
# However, if letters in a version number imply a pre-release version,
|
||||
# the "obvious" thing isn't correct. Eg. you would expect that
|
||||
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
|
||||
# implemented here, this just isn't so.
|
||||
#
|
||||
# Two possible solutions come to mind. The first is to tie the
|
||||
# comparison algorithm to a particular set of semantic rules, as has
|
||||
# been done in the StrictVersion class above. This works great as long
|
||||
# as everyone can go along with bondage and discipline. Hopefully a
|
||||
# (large) subset of Python module programmers will agree that the
|
||||
# particular flavour of bondage and discipline provided by StrictVersion
|
||||
# provides enough benefit to be worth using, and will submit their
|
||||
# version numbering scheme to its domination. The free-thinking
|
||||
# anarchists in the lot will never give in, though, and something needs
|
||||
# to be done to accommodate them.
|
||||
#
|
||||
# Perhaps a "moderately strict" version class could be implemented that
|
||||
# lets almost anything slide (syntactically), and makes some heuristic
|
||||
# assumptions about non-digits in version number strings. This could
|
||||
# sink into special-case-hell, though; if I was as talented and
|
||||
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
|
||||
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
|
||||
# just as happy dealing with things like "2g6" and "1.13++". I don't
|
||||
# think I'm smart enough to do it right though.
|
||||
#
|
||||
# In any case, I've coded the test suite for this module (see
|
||||
# ../test/test_version.py) specifically to fail on things like comparing
|
||||
# "1.2a2" and "1.2". That's not because the *code* is doing anything
|
||||
# wrong, it's because the simple, obvious design doesn't match my
|
||||
# complicated, hairy expectations for real-world version numbers. It
|
||||
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
|
||||
# the Right Thing" (ie. the code matches the conception). But I'd rather
|
||||
# have a conception that matches common notions about version numbers.
|
||||
|
||||
|
||||
class LooseVersion(Version):
|
||||
"""Version numbering for anarchists and software realists.
|
||||
Implements the standard interface for version number classes as
|
||||
described above. A version number consists of a series of numbers,
|
||||
separated by either periods or strings of letters. When comparing
|
||||
version numbers, the numeric components will be compared
|
||||
numerically, and the alphabetic components lexically. The following
|
||||
are all valid version numbers, in no particular order:
|
||||
|
||||
1.5.1
|
||||
1.5.2b2
|
||||
161
|
||||
3.10a
|
||||
8.02
|
||||
3.4j
|
||||
1996.07.12
|
||||
3.2.pl0
|
||||
3.1.1.6
|
||||
2g6
|
||||
11g
|
||||
0.960923
|
||||
2.2beta29
|
||||
1.13++
|
||||
5.5.kw
|
||||
2.0b1pl0
|
||||
|
||||
In fact, there is no such thing as an invalid version number under
|
||||
this scheme; the rules for comparison are simple and predictable,
|
||||
but may not always give the results you want (for some definition
|
||||
of "want").
|
||||
"""
|
||||
|
||||
component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
|
||||
|
||||
def __init__(self, vstring=None):
|
||||
if vstring:
|
||||
self.parse(vstring)
|
||||
|
||||
def parse(self, vstring):
|
||||
# I've given up on thinking I can reconstruct the version string
|
||||
# from the parsed tuple -- so I just store the string here for
|
||||
# use by __str__
|
||||
self.vstring = vstring
|
||||
components = [x for x in self.component_re.split(vstring) if x and x != "."]
|
||||
for i, obj in enumerate(components):
|
||||
try:
|
||||
components[i] = int(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.version = components
|
||||
|
||||
def __str__(self):
|
||||
return self.vstring
|
||||
|
||||
def __repr__(self):
|
||||
return "LooseVersion ('%s')" % str(self)
|
||||
|
||||
def _cmp(self, other):
|
||||
if isinstance(other, str):
|
||||
other = LooseVersion(other)
|
||||
elif not isinstance(other, LooseVersion):
|
||||
return NotImplemented
|
||||
|
||||
if self.version == other.version:
|
||||
return 0
|
||||
if self.version < other.version:
|
||||
return -1
|
||||
if self.version > other.version:
|
||||
return 1
|
||||
|
||||
|
||||
# end class LooseVersion
|
||||
@@ -3,10 +3,22 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from ansible.module_utils.common.validation import check_type_bool
|
||||
|
||||
try:
|
||||
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
|
||||
AnsibleTurboModule as AnsibleModule,
|
||||
) # noqa: F401
|
||||
AnsibleModule.collection_name = "kubernetes.core"
|
||||
except ImportError:
|
||||
enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
|
||||
except TypeError:
|
||||
enable_turbo_mode = False
|
||||
|
||||
if enable_turbo_mode:
|
||||
try:
|
||||
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
|
||||
AnsibleTurboModule as AnsibleModule,
|
||||
) # noqa: F401
|
||||
AnsibleModule.collection_name = "kubernetes.core"
|
||||
except ImportError:
|
||||
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
||||
else:
|
||||
from ansible.module_utils.basic import AnsibleModule # noqa: F401
|
||||
|
||||
@@ -18,10 +18,8 @@ __metaclass__ = type
|
||||
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible.module_utils.six import PY3
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||
|
||||
try:
|
||||
@@ -29,8 +27,6 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
|
||||
|
||||
@@ -79,34 +75,6 @@ STRATEGIC_MERGE_PATCH_KEYS.update(
|
||||
)
|
||||
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
json_loads_byteified = json.loads
|
||||
else:
|
||||
# https://stackoverflow.com/a/33571117
|
||||
def json_loads_byteified(json_text):
|
||||
return _byteify(
|
||||
json.loads(json_text, object_hook=_byteify),
|
||||
ignore_dicts=True
|
||||
)
|
||||
|
||||
def _byteify(data, ignore_dicts=False):
|
||||
# if this is a unicode string, return its string representation
|
||||
if isinstance(data, unicode): # noqa: F821
|
||||
return data.encode('utf-8')
|
||||
# if this is a list of values, return list of byteified values
|
||||
if isinstance(data, list):
|
||||
return [_byteify(item, ignore_dicts=True) for item in data]
|
||||
# if this is a dictionary, return dictionary of byteified keys and values
|
||||
# but only if we haven't already byteified it
|
||||
if isinstance(data, dict) and not ignore_dicts:
|
||||
return {
|
||||
_byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
|
||||
for key, value in data.items()
|
||||
}
|
||||
# if it's anything else, return it in its original form
|
||||
return data
|
||||
|
||||
|
||||
def annotate(desired):
|
||||
return dict(
|
||||
metadata=dict(
|
||||
@@ -123,7 +91,7 @@ def apply_patch(actual, desired):
|
||||
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_byteified(last_applied)
|
||||
last_applied = json.loads(last_applied)
|
||||
patch = merge(dict_merge(last_applied, annotate(last_applied)),
|
||||
dict_merge(desired, annotate(desired)), actual)
|
||||
if patch:
|
||||
@@ -142,7 +110,7 @@ def apply_object(resource, definition):
|
||||
return apply_patch(actual.to_dict(), definition)
|
||||
|
||||
|
||||
def apply(resource, definition):
|
||||
def k8s_apply(resource, definition):
|
||||
existing, desired = apply_object(resource, definition)
|
||||
if not existing:
|
||||
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
|
||||
@@ -284,7 +252,7 @@ def get_delta(last_applied, actual, desired, position=None):
|
||||
elif isinstance(desired_value, list):
|
||||
p = list_merge(last_applied.get(k, []), actual_value, desired_value, this_position)
|
||||
if p:
|
||||
patch[k] = [item for item in p if item]
|
||||
patch[k] = [item for item in p if item is not None]
|
||||
elif actual_value != desired_value:
|
||||
patch[k] = desired_value
|
||||
return patch
|
||||
|
||||
@@ -19,7 +19,7 @@ AUTH_PROXY_HEADERS_SPEC = dict(
|
||||
|
||||
AUTH_ARG_SPEC = {
|
||||
'kubeconfig': {
|
||||
'type': 'path',
|
||||
'type': 'raw',
|
||||
},
|
||||
'context': {},
|
||||
'host': {},
|
||||
|
||||
@@ -18,6 +18,7 @@ import os
|
||||
from collections import defaultdict
|
||||
import hashlib
|
||||
import tempfile
|
||||
from functools import partial
|
||||
|
||||
import kubernetes.dynamic
|
||||
import kubernetes.dynamic.discovery
|
||||
@@ -74,7 +75,7 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
else:
|
||||
try:
|
||||
with open(self.__cache_file, 'r') as f:
|
||||
self._cache = json.load(f, cls=CacheDecoder(self.client))
|
||||
self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
|
||||
if self._cache.get('library_version') != __version__:
|
||||
# Version mismatch, need to refresh cache
|
||||
self.invalidate_cache()
|
||||
@@ -143,7 +144,7 @@ class Discoverer(kubernetes.dynamic.discovery.Discoverer):
|
||||
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]):
|
||||
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:
|
||||
@@ -162,6 +163,10 @@ 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):
|
||||
|
||||
@@ -25,10 +25,21 @@ import traceback
|
||||
import sys
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
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_collections.kubernetes.core.plugins.module_utils.version import (
|
||||
LooseVersion,
|
||||
)
|
||||
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_collections.kubernetes.core.plugins.module_utils.selector import (
|
||||
LabelSelectorFilter,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
@@ -53,7 +64,7 @@ except ImportError as e:
|
||||
|
||||
IMP_K8S_CLIENT = None
|
||||
try:
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.k8sdynamicclient import K8SDynamicClient
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils import k8sdynamicclient
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import LazyDiscoverer
|
||||
IMP_K8S_CLIENT = True
|
||||
except ImportError as e:
|
||||
@@ -119,12 +130,11 @@ def get_api_client(module=None, **kwargs):
|
||||
|
||||
def _raise_or_fail(exc, msg):
|
||||
if module:
|
||||
module.fail_json(msg % to_native(exc))
|
||||
module.fail_json(msg=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):
|
||||
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)
|
||||
@@ -147,7 +157,23 @@ def get_api_client(module=None, **kwargs):
|
||||
auth[true_name] = env_value
|
||||
|
||||
def auth_set(*names):
|
||||
return all([auth.get(name) for name in names])
|
||||
return all(auth.get(name) for name in names)
|
||||
|
||||
def _load_config():
|
||||
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):
|
||||
if LooseVersion(kubernetes.__version__) < LooseVersion("17.17"):
|
||||
_raise_or_fail(Exception("kubernetes >= 17.17.0 is required to use in-memory kubeconfig."), 'Failed to load kubeconfig due to: %s')
|
||||
kubernetes.config.load_kube_config_from_dict(config_dict=kubeconfig, **optional_arg)
|
||||
else:
|
||||
kubernetes.config.load_kube_config(config_file=None, **optional_arg)
|
||||
|
||||
if auth_set('host'):
|
||||
# Removing trailing slashes if any from hostname
|
||||
@@ -158,7 +184,7 @@ def get_api_client(module=None, **kwargs):
|
||||
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'))
|
||||
_load_config()
|
||||
except Exception as err:
|
||||
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
|
||||
|
||||
@@ -168,7 +194,7 @@ def get_api_client(module=None, **kwargs):
|
||||
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'))
|
||||
_load_config()
|
||||
except Exception as err:
|
||||
_raise_or_fail(err, 'Failed to load kubeconfig due to %s')
|
||||
|
||||
@@ -195,7 +221,7 @@ def get_api_client(module=None, **kwargs):
|
||||
return client
|
||||
|
||||
try:
|
||||
client = K8SDynamicClient(kubernetes.client.ApiClient(configuration), discoverer=LazyDiscoverer)
|
||||
client = k8sdynamicclient.K8SDynamicClient(kubernetes.client.ApiClient(configuration), discoverer=LazyDiscoverer)
|
||||
except Exception as err:
|
||||
_raise_or_fail(err, 'Failed to get client due to %s')
|
||||
|
||||
@@ -208,13 +234,13 @@ get_api_client._pool = {}
|
||||
|
||||
class K8sAnsibleMixin(object):
|
||||
|
||||
def __init__(self, module, *args, **kwargs):
|
||||
def __init__(self, module, pyyaml_required=True, *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:
|
||||
if pyyaml_required and not HAS_YAML:
|
||||
module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
||||
|
||||
def find_resource(self, kind, api_version, fail=False):
|
||||
@@ -263,18 +289,28 @@ class K8sAnsibleMixin(object):
|
||||
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)
|
||||
def result_empty(result):
|
||||
return (
|
||||
result is None
|
||||
or result.kind.endswith("List")
|
||||
and not result.get("items")
|
||||
)
|
||||
|
||||
while result_empty(result) and _elapsed() < wait_timeout:
|
||||
try:
|
||||
result = resource.get(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
label_selector=",".join(label_selectors),
|
||||
field_selector=",".join(field_selectors),
|
||||
)
|
||||
except NotFoundError:
|
||||
pass
|
||||
if not result_empty(result):
|
||||
break
|
||||
time.sleep(wait_sleep)
|
||||
if result_empty(result):
|
||||
return dict(resources=[], api_found=True)
|
||||
|
||||
if isinstance(result, ResourceInstance):
|
||||
satisfied_by = []
|
||||
@@ -315,7 +351,7 @@ class K8sAnsibleMixin(object):
|
||||
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:
|
||||
with open(path, "rb") 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))
|
||||
@@ -349,7 +385,7 @@ class K8sAnsibleMixin(object):
|
||||
def fail(self, msg=None):
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state):
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state, label_selectors=None):
|
||||
start = datetime.now()
|
||||
|
||||
def _wait_for_elapsed():
|
||||
@@ -358,7 +394,10 @@ class K8sAnsibleMixin(object):
|
||||
response = None
|
||||
while _wait_for_elapsed() < timeout:
|
||||
try:
|
||||
response = resource.get(name=name, namespace=namespace)
|
||||
params = dict(name=name, namespace=namespace)
|
||||
if label_selectors:
|
||||
params['label_selector'] = ','.join(label_selectors)
|
||||
response = resource.get(**params)
|
||||
if predicate(response):
|
||||
if response:
|
||||
return True, response.to_dict(), _wait_for_elapsed()
|
||||
@@ -371,7 +410,7 @@ class K8sAnsibleMixin(object):
|
||||
response = response.to_dict()
|
||||
return False, response, _wait_for_elapsed()
|
||||
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None):
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None, label_selectors=None):
|
||||
|
||||
def _deployment_ready(deployment):
|
||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
||||
@@ -388,7 +427,7 @@ class K8sAnsibleMixin(object):
|
||||
|
||||
def _pod_ready(pod):
|
||||
return (pod.status and pod.status.containerStatuses is not None
|
||||
and all([container.ready for container in pod.status.containerStatuses]))
|
||||
and all(container.ready for container in pod.status.containerStatuses))
|
||||
|
||||
def _daemonset_ready(daemonset):
|
||||
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None
|
||||
@@ -397,6 +436,14 @@ class K8sAnsibleMixin(object):
|
||||
and daemonset.status.observedGeneration == daemonset.metadata.generation
|
||||
and not daemonset.status.unavailableReplicas)
|
||||
|
||||
def _statefulset_ready(statefulset):
|
||||
return (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 statefulset.status.updatedReplicas == statefulset.spec.replicas
|
||||
and statefulset.status.readyReplicas == statefulset.spec.replicas
|
||||
and statefulset.status.replicas == statefulset.spec.replicas)
|
||||
|
||||
def _custom_condition(resource):
|
||||
if not resource.status or not resource.status.conditions:
|
||||
return False
|
||||
@@ -420,21 +467,22 @@ class K8sAnsibleMixin(object):
|
||||
return False
|
||||
|
||||
def _resource_absent(resource):
|
||||
return not resource
|
||||
return not resource or (resource.kind.endswith('List') and resource.items == [])
|
||||
|
||||
waiter = dict(
|
||||
StatefulSet=_statefulset_ready,
|
||||
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
|
||||
if state == 'present':
|
||||
predicate = waiter.get(kind, lambda x: x) if not condition else _custom_condition
|
||||
else:
|
||||
predicate = _resource_absent
|
||||
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
|
||||
name = definition['metadata']['name']
|
||||
namespace = definition['metadata'].get('namespace')
|
||||
return self._wait_for(resource, name, namespace, predicate, sleep, timeout, state, label_selectors)
|
||||
|
||||
def set_resource_definitions(self, module):
|
||||
resource_definition = module.params.get('resource_definition')
|
||||
@@ -575,6 +623,7 @@ class K8sAnsibleMixin(object):
|
||||
wait_timeout = self.params.get('wait_timeout')
|
||||
wait_condition = None
|
||||
continue_on_error = self.params.get('continue_on_error')
|
||||
label_selectors = self.params.get('label_selectors')
|
||||
if self.params.get('wait_condition') and self.params['wait_condition'].get('type'):
|
||||
wait_condition = self.params['wait_condition']
|
||||
|
||||
@@ -591,6 +640,8 @@ class K8sAnsibleMixin(object):
|
||||
params = dict(name=name)
|
||||
if namespace:
|
||||
params['namespace'] = namespace
|
||||
if label_selectors:
|
||||
params['label_selector'] = ','.join(label_selectors)
|
||||
existing = resource.get(**params)
|
||||
except (NotFoundError, MethodNotAllowedError):
|
||||
# Remove traceback so that it doesn't show up in later failures
|
||||
@@ -625,7 +676,13 @@ class K8sAnsibleMixin(object):
|
||||
|
||||
if state == 'absent':
|
||||
result['method'] = "delete"
|
||||
if not existing:
|
||||
|
||||
def _empty_resource_list():
|
||||
if existing and existing.kind.endswith('List'):
|
||||
return existing.items == []
|
||||
return False
|
||||
|
||||
if not existing or _empty_resource_list():
|
||||
# The object already does not exist
|
||||
return result
|
||||
else:
|
||||
@@ -651,7 +708,7 @@ class K8sAnsibleMixin(object):
|
||||
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')
|
||||
success, resource, duration = self.wait(resource, definition, wait_sleep, wait_timeout, 'absent', label_selectors=label_selectors)
|
||||
result['duration'] = duration
|
||||
if not success:
|
||||
msg = "Resource deletion timed out"
|
||||
@@ -663,6 +720,13 @@ class K8sAnsibleMixin(object):
|
||||
return result
|
||||
|
||||
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=definition['kind'], name=origin_name, namespace=namespace)
|
||||
return result
|
||||
if apply:
|
||||
if self.check_mode:
|
||||
ignored, patch = apply_object(resource, _encode_stringdata(definition))
|
||||
@@ -693,7 +757,8 @@ class K8sAnsibleMixin(object):
|
||||
existing = {}
|
||||
match, diffs = self.diff_objects(existing, result['result'])
|
||||
result['changed'] = not match
|
||||
result['diff'] = diffs
|
||||
if self.module._diff:
|
||||
result['diff'] = diffs
|
||||
result['method'] = 'apply'
|
||||
if not success:
|
||||
msg = "Resource apply timed out"
|
||||
@@ -785,7 +850,8 @@ class K8sAnsibleMixin(object):
|
||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
||||
result['changed'] = not match
|
||||
result['method'] = 'replace'
|
||||
result['diff'] = diffs
|
||||
if self.module._diff:
|
||||
result['diff'] = diffs
|
||||
if not success:
|
||||
msg = "Resource replacement timed out"
|
||||
if continue_on_error:
|
||||
@@ -819,7 +885,8 @@ class K8sAnsibleMixin(object):
|
||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
||||
result['changed'] = not match
|
||||
result['method'] = 'patch'
|
||||
result['diff'] = diffs
|
||||
if self.module._diff:
|
||||
result['diff'] = diffs
|
||||
|
||||
if not success:
|
||||
msg = "Resource update timed out"
|
||||
|
||||
@@ -19,7 +19,7 @@ __metaclass__ = type
|
||||
|
||||
from kubernetes.dynamic import DynamicClient
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import apply
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ApplyException
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class K8SDynamicClient(DynamicClient):
|
||||
if resource.namespaced:
|
||||
body['metadata']['namespace'] = super().ensure_namespace(resource, namespace, body)
|
||||
try:
|
||||
return apply(resource, body)
|
||||
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))
|
||||
|
||||
71
plugins/module_utils/selector.py
Normal file
71
plugins/module_utils/selector.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# 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]
|
||||
self._data = [no_whitespace_data[idx + len(op):]]
|
||||
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)
|
||||
18
plugins/module_utils/version.py
Normal file
18
plugins/module_utils/version.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
"""Provide version object to compare version numbers."""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
|
||||
# remove the _version.py file, and replace the following import by
|
||||
#
|
||||
# from ansible.module_utils.compat.version import LooseVersion
|
||||
|
||||
from ._version import LooseVersion # noqa: F401
|
||||
@@ -130,6 +130,7 @@ options:
|
||||
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"
|
||||
@@ -139,6 +140,12 @@ options:
|
||||
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"
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.helm_common_options
|
||||
'''
|
||||
@@ -357,7 +364,7 @@ def fetch_chart_info(module, command, chart_ref):
|
||||
|
||||
|
||||
def deploy(command, release_name, release_values, chart_name, wait,
|
||||
wait_timeout, disable_hook, force, values_files, atomic=False,
|
||||
wait_timeout, disable_hook, force, values_files, history_max, atomic=False,
|
||||
create_namespace=False, replace=False, skip_crds=False):
|
||||
"""
|
||||
Install/upgrade/rollback release chart
|
||||
@@ -404,8 +411,10 @@ def deploy(command, release_name, release_values, chart_name, wait,
|
||||
if skip_crds:
|
||||
deploy_command += " --skip-crds"
|
||||
|
||||
deploy_command += " " + release_name + " " + chart_name
|
||||
if history_max is not None:
|
||||
deploy_command += " --history-max=%s" % str(history_max)
|
||||
|
||||
deploy_command += " " + release_name + " " + chart_name
|
||||
return deploy_command
|
||||
|
||||
|
||||
@@ -532,6 +541,7 @@ def main():
|
||||
create_namespace=dict(type='bool', default=False),
|
||||
replace=dict(type='bool', default=False),
|
||||
skip_crds=dict(type='bool', default=False),
|
||||
history_max=dict(type='int'),
|
||||
|
||||
# Generic auth key
|
||||
host=dict(type='str', fallback=(env_fallback, ['K8S_AUTH_HOST'])),
|
||||
@@ -546,6 +556,7 @@ def main():
|
||||
mutually_exclusive=[
|
||||
("context", "ca_cert"),
|
||||
("kubeconfig", "ca_cert"),
|
||||
("replace", "history_max"),
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -575,6 +586,7 @@ def main():
|
||||
create_namespace = module.params.get('create_namespace')
|
||||
replace = module.params.get('replace')
|
||||
skip_crds = module.params.get('skip_crds')
|
||||
history_max = module.params.get('history_max')
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
@@ -610,7 +622,7 @@ def main():
|
||||
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)
|
||||
skip_crds=skip_crds, history_max=history_max)
|
||||
changed = True
|
||||
|
||||
else:
|
||||
@@ -627,7 +639,7 @@ def main():
|
||||
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)
|
||||
skip_crds=skip_crds, history_max=history_max)
|
||||
changed = True
|
||||
|
||||
if module.check_mode:
|
||||
|
||||
@@ -108,6 +108,7 @@ options:
|
||||
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,6 +136,12 @@ 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
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -208,6 +215,15 @@ 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
|
||||
@@ -335,6 +351,7 @@ def argspec():
|
||||
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')
|
||||
|
||||
return argument_spec
|
||||
|
||||
|
||||
493
plugins/modules/k8s_cp.py
Normal file
493
plugins/modules/k8s_cp.py
Normal file
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Aubin Bikouo <@abikouo>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_cp
|
||||
|
||||
short_description: Copy files and directories to and from pod.
|
||||
|
||||
version_added: "2.2.0"
|
||||
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
|
||||
description:
|
||||
- Use the Kubernetes Python client to copy files and directories to and from containers inside a pod.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.k8s_auth_options
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "kubernetes >= 12.0.0"
|
||||
|
||||
options:
|
||||
namespace:
|
||||
description:
|
||||
- The pod namespace name.
|
||||
type: str
|
||||
required: yes
|
||||
pod:
|
||||
description:
|
||||
- The pod name.
|
||||
type: str
|
||||
required: yes
|
||||
container:
|
||||
description:
|
||||
- The name of the container in the pod to copy files/directories from/to.
|
||||
- Defaults to the only container if there is only one container in the pod.
|
||||
type: str
|
||||
remote_path:
|
||||
description:
|
||||
- Path of the file or directory to copy.
|
||||
type: path
|
||||
required: yes
|
||||
local_path:
|
||||
description:
|
||||
- Path of the local file or directory.
|
||||
- Required when I(state) is set to C(from_pod).
|
||||
- Mutually exclusive with I(content).
|
||||
type: path
|
||||
content:
|
||||
description:
|
||||
- When used instead of I(local_path), sets the contents of a local file directly to the specified value.
|
||||
- Works only when I(remote_path) is a file. Creates the file if it does not exist.
|
||||
- For advanced formatting or if the content contains a variable, use the M(ansible.builtin.template) module.
|
||||
- Mutually exclusive with I(local_path).
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- When set to C(to_pod), the local I(local_path) file or directory will be copied to I(remote_path) into the pod.
|
||||
- When set to C(from_pod), the remote file or directory I(remote_path) from pod will be copied locally to I(local_path).
|
||||
type: str
|
||||
default: to_pod
|
||||
choices: [ to_pod, from_pod ]
|
||||
no_preserve:
|
||||
description:
|
||||
- The copied file/directory's ownership and permissions will not be preserved in the container.
|
||||
- This option is ignored when I(content) is set or when I(state) is set to C(from_pod).
|
||||
type: bool
|
||||
default: False
|
||||
|
||||
notes:
|
||||
- the tar binary is required on the container when copying from local filesystem to pod.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
|
||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/bar
|
||||
local_path: /tmp/foo
|
||||
|
||||
# kubectl cp /tmp/foo_dir some-namespace/some-pod:/tmp/bar_dir
|
||||
- name: Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/bar_dir
|
||||
local_path: /tmp/foo_dir
|
||||
|
||||
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar -c some-container
|
||||
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
container: some-container
|
||||
remote_path: /tmp/bar
|
||||
local_path: /tmp/foo
|
||||
no_preserve: True
|
||||
state: to_pod
|
||||
|
||||
# kubectl cp some-namespace/some-pod:/tmp/foo /tmp/bar
|
||||
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||
kubernetes.core.k8s_cp:
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/foo
|
||||
local_path: /tmp/bar
|
||||
state: from_pod
|
||||
|
||||
# copy content into a file in the remote pod
|
||||
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
|
||||
kubernetes.core.k8s_cp:
|
||||
state: to_pod
|
||||
namespace: some-namespace
|
||||
pod: some-pod
|
||||
remote_path: /tmp/foo.txt
|
||||
content: "This content will be copied into remote file"
|
||||
'''
|
||||
|
||||
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- message describing the copy operation successfully done.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
import copy
|
||||
import os
|
||||
from tempfile import TemporaryFile, NamedTemporaryFile
|
||||
from select import select
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import tarfile
|
||||
|
||||
# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import K8sAnsibleMixin, get_api_client
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC
|
||||
|
||||
try:
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes.stream import stream
|
||||
from kubernetes.stream.ws_client import STDOUT_CHANNEL, STDERR_CHANNEL, ERROR_CHANNEL, ABNF
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
# ImportError are managed by the common module already.
|
||||
pass
|
||||
|
||||
|
||||
class K8SCopy(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, module, client):
|
||||
self.client = client
|
||||
self.module = module
|
||||
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
||||
|
||||
self.local_path = module.params.get('local_path')
|
||||
self.name = module.params.get('pod')
|
||||
self.namespace = module.params.get('namespace')
|
||||
self.remote_path = module.params.get('remote_path')
|
||||
self.content = module.params.get('content')
|
||||
|
||||
self.no_preserve = module.params.get('no_preserve')
|
||||
self.container_arg = {}
|
||||
if module.params.get('container'):
|
||||
self.container_arg['container'] = module.params.get('container')
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
|
||||
class K8SCopyFromPod(K8SCopy):
|
||||
"""
|
||||
Copy files/directory from Pod into local filesystem
|
||||
"""
|
||||
def __init__(self, module, client):
|
||||
super(K8SCopyFromPod, self).__init__(module, client)
|
||||
self.is_remote_path_dir = None
|
||||
self.files_to_copy = list()
|
||||
|
||||
def list_remote_files(self):
|
||||
"""
|
||||
This method will check if the remote path is a dir or file
|
||||
if it is a directory the file list will be updated accordingly
|
||||
"""
|
||||
try:
|
||||
find_cmd = ['find', self.remote_path, '-type', 'f', '-name', '*']
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=find_cmd,
|
||||
stdout=True, stderr=True,
|
||||
stdin=False, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to execute on pod {0}/{1} due to : {2}".format(self.namespace, self.name, to_native(e)))
|
||||
stderr = []
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
if response.peek_stdout():
|
||||
self.files_to_copy.extend(response.read_stdout().rstrip('\n').split('\n'))
|
||||
if response.peek_stderr():
|
||||
err = response.read_stderr()
|
||||
if "No such file or directory" in err:
|
||||
self.module.fail_json(msg="{0} does not exist in remote pod filesystem".format(self.remote_path))
|
||||
stderr.append(err)
|
||||
error = response.read_channel(ERROR_CHANNEL)
|
||||
response.close()
|
||||
error = yaml.safe_load(error)
|
||||
if error['status'] != 'Success':
|
||||
self.module.fail_json(msg="Failed to execute on Pod due to: {0}".format(error))
|
||||
|
||||
def read(self):
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
|
||||
if self.response.is_open():
|
||||
if not self.response.sock.connected:
|
||||
self.response._connected = False
|
||||
else:
|
||||
ret, out, err = select((self.response.sock.sock, ), (), (), 0)
|
||||
if ret:
|
||||
code, frame = self.response.sock.recv_data_frame(True)
|
||||
if code == ABNF.OPCODE_CLOSE:
|
||||
self.response._connected = False
|
||||
elif code in (ABNF.OPCODE_BINARY, ABNF.OPCODE_TEXT) and len(frame.data) > 1:
|
||||
channel = frame.data[0]
|
||||
content = frame.data[1:]
|
||||
if content:
|
||||
if channel == STDOUT_CHANNEL:
|
||||
self.stdout = content
|
||||
elif channel == STDERR_CHANNEL:
|
||||
self.stderr = content.decode("utf-8", "replace")
|
||||
|
||||
def copy(self):
|
||||
is_remote_path_dir = len(self.files_to_copy) > 1 or self.files_to_copy[0] != self.remote_path
|
||||
relpath_start = self.remote_path
|
||||
if is_remote_path_dir and os.path.isdir(self.local_path):
|
||||
relpath_start = os.path.dirname(self.remote_path)
|
||||
|
||||
for remote_file in self.files_to_copy:
|
||||
dest_file = self.local_path
|
||||
if is_remote_path_dir:
|
||||
dest_file = os.path.join(self.local_path, os.path.relpath(remote_file, start=relpath_start))
|
||||
# create directory to copy file in
|
||||
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
|
||||
|
||||
pod_command = ['cat', remote_file]
|
||||
self.response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stderr=True, stdin=True,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
errors = []
|
||||
with open(dest_file, 'wb') as fh:
|
||||
while self.response._connected:
|
||||
self.read()
|
||||
if self.stdout:
|
||||
fh.write(self.stdout)
|
||||
if self.stderr:
|
||||
errors.append(self.stderr)
|
||||
if errors:
|
||||
self.module.fail_json(msg="Failed to copy file from Pod: {0}".format(''.join(errors)))
|
||||
self.module.exit_json(changed=True, result="{0} successfully copied locally into {1}".format(self.remote_path, self.local_path))
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.list_remote_files()
|
||||
if self.files_to_copy == []:
|
||||
self.module.exit_json(changed=False, warning="No file found from directory '{0}' into remote Pod.".format(self.remote_path))
|
||||
self.copy()
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to copy file/directory from Pod due to: {0}".format(to_native(e)))
|
||||
|
||||
|
||||
class K8SCopyToPod(K8SCopy):
|
||||
"""
|
||||
Copy files/directory from local filesystem into remote Pod
|
||||
"""
|
||||
def __init__(self, module, client):
|
||||
super(K8SCopyToPod, self).__init__(module, client)
|
||||
self.files_to_copy = list()
|
||||
|
||||
def run_from_pod(self, command):
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=command,
|
||||
stderr=True, stdin=False,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
errors = []
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
if response.peek_stderr():
|
||||
errors.append(response.read_stderr())
|
||||
response.close()
|
||||
err = response.read_channel(ERROR_CHANNEL)
|
||||
err = yaml.safe_load(err)
|
||||
response.close()
|
||||
if err['status'] != 'Success':
|
||||
self.module.fail_json(msg="Failed to run {0} on Pod.".format(command), errors=errors)
|
||||
|
||||
def is_remote_path_dir(self):
|
||||
pod_command = ['test', '-d', self.remote_path]
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=pod_command,
|
||||
stdout=True, stderr=True,
|
||||
stdin=False, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
while response.is_open():
|
||||
response.update(timeout=1)
|
||||
err = response.read_channel(ERROR_CHANNEL)
|
||||
err = yaml.safe_load(err)
|
||||
response.close()
|
||||
if err['status'] == 'Success':
|
||||
return True
|
||||
return False
|
||||
|
||||
def close_temp_file(self):
|
||||
if self.named_temp_file:
|
||||
self.named_temp_file.close()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
# remove trailing slash from destination path
|
||||
dest_file = self.remote_path.rstrip("/")
|
||||
src_file = self.local_path
|
||||
self.named_temp_file = None
|
||||
if self.content:
|
||||
self.named_temp_file = NamedTemporaryFile(mode="w")
|
||||
self.named_temp_file.write(self.content)
|
||||
self.named_temp_file.flush()
|
||||
src_file = self.named_temp_file.name
|
||||
else:
|
||||
if not os.path.exists(self.local_path):
|
||||
self.module.fail_json(msg="{0} does not exist in local filesystem".format(self.local_path))
|
||||
if not os.access(self.local_path, os.R_OK):
|
||||
self.module.fail_json(msg="{0} not readable".format(self.local_path))
|
||||
|
||||
if self.is_remote_path_dir():
|
||||
if self.content:
|
||||
self.module.fail_json(msg="When content is specified, remote path should not be an existing directory")
|
||||
else:
|
||||
dest_file = os.path.join(dest_file, os.path.basename(src_file))
|
||||
|
||||
if self.no_preserve:
|
||||
tar_command = ['tar', '--no-same-permissions', '--no-same-owner', '-xmf', '-']
|
||||
else:
|
||||
tar_command = ['tar', '-xmf', '-']
|
||||
|
||||
if dest_file.startswith("/"):
|
||||
tar_command.extend(['-C', '/'])
|
||||
|
||||
response = stream(self.api_instance.connect_get_namespaced_pod_exec,
|
||||
self.name,
|
||||
self.namespace,
|
||||
command=tar_command,
|
||||
stderr=True, stdin=True,
|
||||
stdout=True, tty=False,
|
||||
_preload_content=False, **self.container_arg)
|
||||
with TemporaryFile() as tar_buffer:
|
||||
with tarfile.open(fileobj=tar_buffer, mode='w') as tar:
|
||||
tar.add(src_file, dest_file)
|
||||
tar_buffer.seek(0)
|
||||
commands = []
|
||||
# push command in chunk mode
|
||||
size = 1024 * 1024
|
||||
while True:
|
||||
data = tar_buffer.read(size)
|
||||
if not data:
|
||||
break
|
||||
commands.append(data)
|
||||
|
||||
stderr, stdout = [], []
|
||||
while response.is_open():
|
||||
if response.peek_stdout():
|
||||
stdout.append(response.read_stdout().rstrip("\n"))
|
||||
if response.peek_stderr():
|
||||
stderr.append(response.read_stderr().rstrip("\n"))
|
||||
if commands:
|
||||
cmd = commands.pop(0)
|
||||
response.write_stdin(cmd)
|
||||
else:
|
||||
break
|
||||
response.close()
|
||||
if stderr:
|
||||
self.close_temp_file()
|
||||
self.module.fail_json(command=tar_command, msg="Failed to copy local file/directory into Pod due to: {0}".format(''.join(stderr)))
|
||||
self.close_temp_file()
|
||||
if self.content:
|
||||
self.module.exit_json(changed=True, result="Content successfully copied into {0} on remote Pod".format(self.remote_path))
|
||||
self.module.exit_json(changed=True, result="{0} successfully copied into remote Pod into {1}".format(self.local_path, self.remote_path))
|
||||
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to copy local file/directory into Pod due to: {0}".format(to_native(e)))
|
||||
|
||||
|
||||
def check_pod(k8s_ansible_mixin, module):
|
||||
resource = k8s_ansible_mixin.find_resource("Pod", None, True)
|
||||
namespace = module.params.get('namespace')
|
||||
name = module.params.get('pod')
|
||||
container = module.params.get('container')
|
||||
|
||||
def _fail(exc):
|
||||
arg = {}
|
||||
if hasattr(exc, 'body'):
|
||||
msg = "Namespace={0} Kind=Pod Name={1}: Failed requested object: {2}".format(namespace, name, exc.body)
|
||||
else:
|
||||
msg = to_native(exc)
|
||||
for attr in ['status', 'reason']:
|
||||
if hasattr(exc, attr):
|
||||
arg[attr] = getattr(exc, attr)
|
||||
module.fail_json(msg=msg, **arg)
|
||||
|
||||
try:
|
||||
result = resource.get(name=name, namespace=namespace)
|
||||
containers = [c['name'] for c in result.to_dict()['status']['containerStatuses']]
|
||||
if container and container not in containers:
|
||||
module.fail_json(msg="Pod has no container {0}".format(container))
|
||||
return containers
|
||||
except Exception as exc:
|
||||
_fail(exc)
|
||||
|
||||
|
||||
def execute_module(module):
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module, pyyaml_required=False)
|
||||
k8s_ansible_mixin.check_library_version()
|
||||
|
||||
k8s_ansible_mixin.module = module
|
||||
k8s_ansible_mixin.argspec = module.argument_spec
|
||||
k8s_ansible_mixin.params = k8s_ansible_mixin.module.params
|
||||
k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json
|
||||
k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json
|
||||
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
containers = check_pod(k8s_ansible_mixin, module)
|
||||
if len(containers) > 1 and module.params.get('container') is None:
|
||||
module.fail_json(msg="Pod contains more than 1 container, option 'container' should be set")
|
||||
|
||||
try:
|
||||
load_class = {'to_pod': K8SCopyToPod, 'from_pod': K8SCopyFromPod}
|
||||
state = module.params.get('state')
|
||||
k8s_copy = load_class.get(state)(module, k8s_ansible_mixin.client)
|
||||
k8s_copy.run()
|
||||
except Exception as e:
|
||||
module.fail_json("Failed to copy object due to: {0}".format(to_native(e)))
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
argument_spec['namespace'] = {'type': 'str', 'required': True}
|
||||
argument_spec['pod'] = {'type': 'str', 'required': True}
|
||||
argument_spec['container'] = {}
|
||||
argument_spec['remote_path'] = {'type': 'path', 'required': True}
|
||||
argument_spec['local_path'] = {'type': 'path'}
|
||||
argument_spec['content'] = {'type': 'str'}
|
||||
argument_spec['state'] = {'type': 'str', 'default': 'to_pod', 'choices': ['to_pod', 'from_pod']}
|
||||
argument_spec['no_preserve'] = {'type': 'bool', 'default': False}
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=[('local_path', 'content')],
|
||||
required_if=[('state', 'from_pod', ['local_path'])],
|
||||
required_one_of=[['local_path', 'content']],
|
||||
supports_check_mode=True)
|
||||
|
||||
execute_module(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
441
plugins/modules/k8s_drain.py
Normal file
441
plugins/modules/k8s_drain.py
Normal file
@@ -0,0 +1,441 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021, Aubin Bikouo <@abikouo>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_drain
|
||||
|
||||
short_description: Drain, Cordon, or Uncordon node in k8s cluster
|
||||
|
||||
version_added: "2.2.0"
|
||||
|
||||
author: Aubin Bikouo (@abikouo)
|
||||
|
||||
description:
|
||||
- Drain node in preparation for maintenance same as kubectl drain.
|
||||
- Cordon will mark the node as unschedulable.
|
||||
- Uncordon will mark the node as schedulable.
|
||||
- The given node will be marked unschedulable to prevent new pods from arriving.
|
||||
- Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
|
||||
|
||||
extends_documentation_fragment:
|
||||
- kubernetes.core.k8s_auth_options
|
||||
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Determines whether to drain, cordon, or uncordon node.
|
||||
type: str
|
||||
default: drain
|
||||
choices: [ cordon, drain, uncordon ]
|
||||
name:
|
||||
description:
|
||||
- The name of the node.
|
||||
required: true
|
||||
type: str
|
||||
delete_options:
|
||||
type: dict
|
||||
description:
|
||||
- Specify options to delete pods.
|
||||
- This option has effect only when C(state) is set to I(drain).
|
||||
suboptions:
|
||||
terminate_grace_period:
|
||||
description:
|
||||
- Specify how many seconds to wait before forcefully terminating.
|
||||
- If not specified, the default grace period for the object type will be used.
|
||||
- The value zero indicates delete immediately.
|
||||
required: false
|
||||
type: int
|
||||
force:
|
||||
description:
|
||||
- Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.
|
||||
type: bool
|
||||
default: False
|
||||
ignore_daemonsets:
|
||||
description:
|
||||
- Ignore DaemonSet-managed pods.
|
||||
type: bool
|
||||
default: False
|
||||
disable_eviction:
|
||||
description:
|
||||
- Forces drain to use delete rather than evict.
|
||||
type: bool
|
||||
default: False
|
||||
wait_timeout:
|
||||
description:
|
||||
- The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.
|
||||
type: int
|
||||
wait_sleep:
|
||||
description:
|
||||
- Number of seconds to sleep between checks.
|
||||
- Ignored if C(wait_timeout) is not set.
|
||||
default: 5
|
||||
type: int
|
||||
|
||||
requirements:
|
||||
- python >= 3.6
|
||||
- kubernetes >= 12.0.0
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: drain
|
||||
name: foo
|
||||
force: yes
|
||||
|
||||
- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: drain
|
||||
name: foo
|
||||
delete_options:
|
||||
terminate_grace_period: 900
|
||||
|
||||
- name: Mark node "foo" as schedulable.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: uncordon
|
||||
name: foo
|
||||
|
||||
- name: Mark node "foo" as unschedulable.
|
||||
kubernetes.core.k8s_drain:
|
||||
state: cordon
|
||||
name: foo
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- The node status and the number of pods deleted.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
import copy
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from datetime import datetime
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
||||
AnsibleModule,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes.client.models import V1DeleteOptions
|
||||
from kubernetes.client.exceptions import ApiException
|
||||
except ImportError:
|
||||
# ImportError are managed by the common module already.
|
||||
pass
|
||||
|
||||
HAS_EVICTION_API = True
|
||||
k8s_import_exception = None
|
||||
K8S_IMP_ERR = None
|
||||
|
||||
try:
|
||||
from kubernetes.client.models import V1beta1Eviction as v1_eviction
|
||||
except ImportError:
|
||||
try:
|
||||
from kubernetes.client.models import V1Eviction as v1_eviction
|
||||
except ImportError as e:
|
||||
k8s_import_exception = e
|
||||
K8S_IMP_ERR = traceback.format_exc()
|
||||
HAS_EVICTION_API = False
|
||||
|
||||
|
||||
def filter_pods(pods, force, ignore_daemonset):
|
||||
k8s_kind_mirror = "kubernetes.io/config.mirror"
|
||||
daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], []
|
||||
for pod in pods:
|
||||
# check mirror pod: cannot be delete using API Server
|
||||
if pod.metadata.annotations and k8s_kind_mirror in pod.metadata.annotations:
|
||||
mirror.append((pod.metadata.namespace, pod.metadata.name))
|
||||
continue
|
||||
|
||||
# Any finished pod can be deleted
|
||||
if pod.status.phase in ('Succeeded', 'Failed'):
|
||||
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
||||
continue
|
||||
|
||||
# Pod with local storage cannot be deleted
|
||||
# TODO: support new option delete-emptydatadir in order to allow deletion of such pod
|
||||
if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes):
|
||||
localStorage.append((pod.metadata.namespace, pod.metadata.name))
|
||||
continue
|
||||
|
||||
# Check replicated Pod
|
||||
owner_ref = pod.metadata.owner_references
|
||||
if not owner_ref:
|
||||
unmanaged.append((pod.metadata.namespace, pod.metadata.name))
|
||||
else:
|
||||
for owner in owner_ref:
|
||||
if owner.kind == "DaemonSet":
|
||||
daemonSet.append((pod.metadata.namespace, pod.metadata.name))
|
||||
else:
|
||||
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
||||
|
||||
warnings, errors = [], []
|
||||
if unmanaged:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in unmanaged])
|
||||
if not force:
|
||||
errors.append("cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
|
||||
" DaemonSet or StatefulSet (use option force set to yes): {0}.".format(pod_names))
|
||||
else:
|
||||
# Pod not managed will be deleted as 'force' is true
|
||||
warnings.append("Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(pod_names))
|
||||
to_delete += unmanaged
|
||||
|
||||
# mirror pods warning
|
||||
if mirror:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in mirror])
|
||||
warnings.append("cannot delete mirror Pods using API server: {0}.".format(pod_names))
|
||||
|
||||
# local storage
|
||||
if localStorage:
|
||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage])
|
||||
errors.append("cannot delete Pods with local storage: {0}.".format(pod_names))
|
||||
|
||||
# DaemonSet managed Pods
|
||||
if daemonSet:
|
||||
pod_names = ','.join([pod[0] + "/" + pod[1] for pod in daemonSet])
|
||||
if not ignore_daemonset:
|
||||
errors.append("cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(pod_names))
|
||||
else:
|
||||
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
||||
return to_delete, warnings, errors
|
||||
|
||||
|
||||
class K8sDrainAnsible(object):
|
||||
|
||||
def __init__(self, module):
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, get_api_client)
|
||||
|
||||
self._module = module
|
||||
self._k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
self._k8s_ansible_mixin.client = get_api_client(module=self._module)
|
||||
|
||||
self._k8s_ansible_mixin.module = self._module
|
||||
self._k8s_ansible_mixin.argspec = self._module.argument_spec
|
||||
self._k8s_ansible_mixin.check_mode = self._module.check_mode
|
||||
self._k8s_ansible_mixin.params = self._module.params
|
||||
self._k8s_ansible_mixin.fail_json = self._module.fail_json
|
||||
self._k8s_ansible_mixin.fail = self._module.fail_json
|
||||
self._k8s_ansible_mixin.exit_json = self._module.exit_json
|
||||
self._k8s_ansible_mixin.warn = self._module.warn
|
||||
self._k8s_ansible_mixin.warnings = []
|
||||
|
||||
self._api_instance = core_v1_api.CoreV1Api(self._k8s_ansible_mixin.client.client)
|
||||
self._k8s_ansible_mixin.check_library_version()
|
||||
|
||||
# delete options
|
||||
self._drain_options = module.params.get('delete_options', {})
|
||||
self._delete_options = None
|
||||
if self._drain_options.get('terminate_grace_period'):
|
||||
self._delete_options = {}
|
||||
self._delete_options.update({'apiVersion': 'v1'})
|
||||
self._delete_options.update({'kind': 'DeleteOptions'})
|
||||
self._delete_options.update({'gracePeriodSeconds': self._drain_options.get('terminate_grace_period')})
|
||||
|
||||
self._changed = False
|
||||
|
||||
def wait_for_pod_deletion(self, pods, wait_timeout, wait_sleep):
|
||||
start = datetime.now()
|
||||
|
||||
def _elapsed_time():
|
||||
return (datetime.now() - start).seconds
|
||||
|
||||
response = None
|
||||
pod = pods.pop()
|
||||
while (_elapsed_time() < wait_timeout or wait_timeout == 0) and pods:
|
||||
if not pod:
|
||||
pod = pods.pop()
|
||||
try:
|
||||
response = self._api_instance.read_namespaced_pod(namespace=pod[0], name=pod[1])
|
||||
if not response:
|
||||
pod = None
|
||||
time.sleep(wait_sleep)
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
self._module.fail_json(msg="Exception raised: {0}".format(exc.reason))
|
||||
pod = None
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Exception raised: {0}".format(to_native(e)))
|
||||
if not pods:
|
||||
return None
|
||||
return "timeout reached while pods were still running."
|
||||
|
||||
def evict_pods(self, pods):
|
||||
for namespace, name in pods:
|
||||
definition = {
|
||||
'metadata': {
|
||||
'name': name,
|
||||
'namespace': namespace
|
||||
}
|
||||
}
|
||||
if self._delete_options:
|
||||
definition.update({'delete_options': self._delete_options})
|
||||
try:
|
||||
if self._drain_options.get('disable_eviction'):
|
||||
body = V1DeleteOptions(**definition)
|
||||
self._api_instance.delete_namespaced_pod(name=name, namespace=namespace, body=body)
|
||||
else:
|
||||
body = v1_eviction(**definition)
|
||||
self._api_instance.create_namespaced_pod_eviction(
|
||||
name=name, namespace=namespace, body=body
|
||||
)
|
||||
self._changed = True
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, exc.reason))
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to delete pod {0}/{1} due to: {2}".format(namespace, name, to_native(exc)))
|
||||
|
||||
def delete_or_evict_pods(self, node_unschedulable):
|
||||
# Mark node as unschedulable
|
||||
result = []
|
||||
if not node_unschedulable:
|
||||
self.patch_node(unschedulable=True)
|
||||
result.append("node {0} marked unschedulable.".format(self._module.params.get('name')))
|
||||
self._changed = True
|
||||
else:
|
||||
result.append("node {0} already marked unschedulable.".format(self._module.params.get('name')))
|
||||
|
||||
def _revert_node_patch():
|
||||
if self._changed:
|
||||
self._changed = False
|
||||
self.patch_node(unschedulable=False)
|
||||
|
||||
try:
|
||||
field_selector = "spec.nodeName={name}".format(name=self._module.params.get('name'))
|
||||
pod_list = self._api_instance.list_pod_for_all_namespaces(field_selector=field_selector)
|
||||
# Filter pods
|
||||
force = self._drain_options.get('force', False)
|
||||
ignore_daemonset = self._drain_options.get('ignore_daemonsets', False)
|
||||
pods, warnings, errors = filter_pods(pod_list.items, force, ignore_daemonset)
|
||||
if errors:
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Pod deletion errors: {0}".format(" ".join(errors)))
|
||||
except ApiException as exc:
|
||||
if exc.reason != "Not Found":
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Failed to list pod from node {name} due to: {reason}".format(
|
||||
name=self._module.params.get('name'), reason=exc.reason), status=exc.status)
|
||||
pods = []
|
||||
except Exception as exc:
|
||||
_revert_node_patch()
|
||||
self._module.fail_json(msg="Failed to list pod from node {name} due to: {error}".format(
|
||||
name=self._module.params.get('name'), error=to_native(exc)))
|
||||
|
||||
# Delete Pods
|
||||
if pods:
|
||||
self.evict_pods(pods)
|
||||
number_pod = len(pods)
|
||||
if self._drain_options.get('wait_timeout') is not None:
|
||||
warn = self.wait_for_pod_deletion(pods,
|
||||
self._drain_options.get('wait_timeout'),
|
||||
self._drain_options.get('wait_sleep'))
|
||||
if warn:
|
||||
warnings.append(warn)
|
||||
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
||||
if warnings:
|
||||
return dict(result=' '.join(result), warnings=warnings)
|
||||
return dict(result=' '.join(result))
|
||||
|
||||
def patch_node(self, unschedulable):
|
||||
|
||||
body = {
|
||||
'spec': {'unschedulable': unschedulable}
|
||||
}
|
||||
try:
|
||||
self._api_instance.patch_node(name=self._module.params.get('name'), body=body)
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to patch node due to: {0}".format(to_native(exc)))
|
||||
|
||||
def execute_module(self):
|
||||
|
||||
state = self._module.params.get('state')
|
||||
name = self._module.params.get('name')
|
||||
try:
|
||||
node = self._api_instance.read_node(name=name)
|
||||
except ApiException as exc:
|
||||
if exc.reason == "Not Found":
|
||||
self._module.fail_json(msg="Node {0} not found.".format(name))
|
||||
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, exc.reason), status=exc.status)
|
||||
except Exception as exc:
|
||||
self._module.fail_json(msg="Failed to retrieve node '{0}' due to: {1}".format(name, to_native(exc)))
|
||||
|
||||
result = {}
|
||||
if state == "cordon":
|
||||
if node.spec.unschedulable:
|
||||
self._module.exit_json(result="node {0} already marked unschedulable.".format(name))
|
||||
self.patch_node(unschedulable=True)
|
||||
result['result'] = "node {0} marked unschedulable.".format(name)
|
||||
self._changed = True
|
||||
|
||||
elif state == "uncordon":
|
||||
if not node.spec.unschedulable:
|
||||
self._module.exit_json(result="node {0} already marked schedulable.".format(name))
|
||||
self.patch_node(unschedulable=False)
|
||||
result['result'] = "node {0} marked schedulable.".format(name)
|
||||
self._changed = True
|
||||
|
||||
else:
|
||||
# drain node
|
||||
# Delete or Evict Pods
|
||||
ret = self.delete_or_evict_pods(node_unschedulable=node.spec.unschedulable)
|
||||
result.update(ret)
|
||||
|
||||
self._module.exit_json(changed=self._changed, **result)
|
||||
|
||||
|
||||
def argspec():
|
||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
argument_spec.update(
|
||||
dict(
|
||||
state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
|
||||
name=dict(required=True),
|
||||
delete_options=dict(
|
||||
type='dict',
|
||||
default={},
|
||||
options=dict(
|
||||
terminate_grace_period=dict(type='int'),
|
||||
force=dict(type='bool', default=False),
|
||||
ignore_daemonsets=dict(type='bool', default=False),
|
||||
disable_eviction=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int'),
|
||||
wait_sleep=dict(type='int', default=5),
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=argspec())
|
||||
|
||||
if not HAS_EVICTION_API:
|
||||
module.fail_json(
|
||||
msg="The kubernetes Python library missing with V1Eviction API",
|
||||
exception=K8S_IMP_ERR,
|
||||
error=to_native(k8s_import_exception),
|
||||
)
|
||||
|
||||
k8s_drain = K8sDrainAnsible(module)
|
||||
k8s_drain.execute_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -31,6 +31,7 @@ requirements:
|
||||
- "PyYAML >= 3.11"
|
||||
|
||||
notes:
|
||||
- Return code C(rc) for the command executed is added in output in version 2.2.0, and deprecates return code C(return_code).
|
||||
- Return code C(return_code) for the command executed is added in output in version 1.0.0.
|
||||
- The authenticated user must have at least read access to the pods resource and write access to the pods/exec resource.
|
||||
|
||||
@@ -82,7 +83,7 @@ EXAMPLES = r'''
|
||||
- name: Check last command status
|
||||
debug:
|
||||
msg: "cmd failed"
|
||||
when: command_status.return_code != 0
|
||||
when: command_status.rc != 0
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -104,9 +105,13 @@ result:
|
||||
stderr_lines:
|
||||
description: The command stderr
|
||||
type: str
|
||||
return_code:
|
||||
rc:
|
||||
description: The command status code
|
||||
type: int
|
||||
version_added: 2.2.0
|
||||
return_code:
|
||||
description: The command status code. This attribute is deprecated and will be removed in a future release. Please use rc instead.
|
||||
type: int
|
||||
'''
|
||||
|
||||
import copy
|
||||
@@ -178,11 +183,13 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
else:
|
||||
rc = int(err['details']['causes'][0]['message'])
|
||||
|
||||
module.deprecate("The 'return_code' return key is deprecated. Please use 'rc' instead.", version="4.0.0", collection_name="kubernetes.core")
|
||||
module.exit_json(
|
||||
# Some command might change environment, but ultimately failing at end
|
||||
changed=True,
|
||||
stdout="".join(stdout),
|
||||
stderr="".join(stderr),
|
||||
rc=rc,
|
||||
return_code=rc
|
||||
)
|
||||
|
||||
|
||||
@@ -191,6 +191,10 @@ def main():
|
||||
|
||||
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
||||
k8s_ansible_mixin.client = get_api_client(module=module)
|
||||
k8s_ansible_mixin.fail_json = module.fail_json
|
||||
k8s_ansible_mixin.fail = module.fail_json
|
||||
k8s_ansible_mixin.exit_json = module.exit_json
|
||||
k8s_ansible_mixin.warn = module.warn
|
||||
execute_module(module, k8s_ansible_mixin)
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ short_description: Apply JSON patch operations to existing objects
|
||||
|
||||
description:
|
||||
- This module is used to apply RFC 6902 JSON patch operations only.
|
||||
- Use the M(k8s) module for strategic merge or JSON merge operations.
|
||||
- Use the M(kubernetes.core.k8s) module for strategic merge or JSON merge operations.
|
||||
- The jsonpatch library is required for check mode.
|
||||
|
||||
version_added: 2.0.0
|
||||
@@ -254,7 +254,8 @@ def execute_module(k8s_module, module):
|
||||
success, result['result'], result['duration'] = k8s_module.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
match, diffs = k8s_module.diff_objects(existing.to_dict(), obj)
|
||||
result["changed"] = not match
|
||||
result["diff"] = diffs
|
||||
if module._diff:
|
||||
result["diff"] = diffs
|
||||
|
||||
if not success:
|
||||
msg = "Resource update timed out"
|
||||
|
||||
@@ -54,6 +54,12 @@ options:
|
||||
- If there is more than one container, this option is required.
|
||||
required: no
|
||||
type: str
|
||||
since_seconds:
|
||||
description:
|
||||
- A relative time in seconds before the current time from which to show logs.
|
||||
required: no
|
||||
type: str
|
||||
version_added: '2.2.0'
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -83,6 +89,7 @@ EXAMPLES = r'''
|
||||
kind: Deployment
|
||||
namespace: testing
|
||||
name: example
|
||||
since_seconds: "4000"
|
||||
register: log
|
||||
|
||||
# This will get the log from a single Pod managed by this DeploymentConfig
|
||||
@@ -124,6 +131,7 @@ def argspec():
|
||||
dict(
|
||||
kind=dict(type='str', default='Pod'),
|
||||
container=dict(),
|
||||
since_seconds=dict(),
|
||||
label_selectors=dict(type='list', elements='str', default=[]),
|
||||
)
|
||||
)
|
||||
@@ -159,6 +167,9 @@ def execute_module(module, k8s_ansible_mixin):
|
||||
if module.params.get('container'):
|
||||
kwargs['query_params'] = dict(container=module.params['container'])
|
||||
|
||||
if module.params.get('since_seconds'):
|
||||
kwargs.setdefault('query_params', {}).update({'sinceSeconds': module.params['since_seconds']})
|
||||
|
||||
log = serialize_log(resource.log.get(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
|
||||
@@ -181,7 +181,9 @@ def execute_module(module, k8s_ansible_mixin,):
|
||||
wait_sleep = module.params.get('wait_sleep')
|
||||
existing = None
|
||||
existing_count = None
|
||||
return_attributes = dict(result=dict(), diff=dict())
|
||||
return_attributes = dict(result=dict())
|
||||
if module._diff:
|
||||
return_attributes['diff'] = dict()
|
||||
if wait:
|
||||
return_attributes['duration'] = 0
|
||||
|
||||
@@ -256,7 +258,9 @@ def execute_module(module, k8s_ansible_mixin,):
|
||||
name = existing.metadata.name
|
||||
namespace = existing.metadata.namespace
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
result = {'changed': False, 'result': existing.to_dict(), 'diff': {}}
|
||||
result = {'changed': False, 'result': existing.to_dict()}
|
||||
if module._diff:
|
||||
result['diff'] = {}
|
||||
if wait:
|
||||
result['duration'] = 0
|
||||
# append result to the return attribute
|
||||
@@ -302,7 +306,8 @@ def scale(module, k8s_ansible_mixin, resource, existing_object, replicas, wait,
|
||||
result = dict()
|
||||
result['result'] = k8s_obj
|
||||
result['changed'] = not match
|
||||
result['diff'] = diffs
|
||||
if module._diff:
|
||||
result['diff'] = diffs
|
||||
|
||||
if wait:
|
||||
success, result['result'], result['duration'] = k8s_ansible_mixin.wait(resource, scale_obj, wait_sleep, wait_time)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- cloud.common
|
||||
@@ -1 +1,4 @@
|
||||
kubernetes-validate
|
||||
coverage==4.5.4
|
||||
pytest
|
||||
pytest-xdist
|
||||
|
||||
8
tests/integration/targets/helm/aliases
Normal file
8
tests/integration/targets/helm/aliases
Normal file
@@ -0,0 +1,8 @@
|
||||
# slow - 11min
|
||||
slow
|
||||
time=313
|
||||
helm_info
|
||||
helm_plugin
|
||||
helm_plugin_info
|
||||
helm_repository
|
||||
helm_template
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
helm_archive_name: "helm-{{ helm_version }}-{{ ansible_system | lower }}-amd64.tar.gz"
|
||||
helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
|
||||
helm_namespace: helm
|
||||
|
||||
tiller_namespace: tiller
|
||||
tiller_cluster_role: cluster-admin
|
||||
|
||||
chart_test: "ingress-nginx"
|
||||
chart_test_local_path: "nginx-ingress"
|
||||
@@ -17,3 +13,15 @@ chart_test_git_repo: "http://github.com/helm/charts.git"
|
||||
chart_test_values:
|
||||
revisionHistoryLimit: 0
|
||||
myValue: "changed"
|
||||
|
||||
test_namespace:
|
||||
- "helm-diff"
|
||||
- "helm-envvars"
|
||||
- "helm-uninstall"
|
||||
- "helm-not-installed"
|
||||
- "helm-crd"
|
||||
- "helm-url"
|
||||
- "helm-repository"
|
||||
- "helm-local-path-001"
|
||||
- "helm-local-path-002"
|
||||
- "helm-local-path-003"
|
||||
@@ -1,9 +1,9 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: foos.example.com
|
||||
name: foos.ansible.com
|
||||
spec:
|
||||
group: example.com
|
||||
group: ansible.com
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
95
tests/integration/targets/helm/library/helm_test_version.py
Normal file
95
tests/integration/targets/helm/library/helm_test_version.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2021, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: helm_test_version
|
||||
short_description: check helm executable version
|
||||
author:
|
||||
- Aubin Bikouo (@abikouo)
|
||||
requirements:
|
||||
- "helm (https://github.com/helm/helm/releases)"
|
||||
description:
|
||||
- validate version of helm binary is lower than the specified version.
|
||||
options:
|
||||
binary_path:
|
||||
description:
|
||||
- The path of a helm binary to use.
|
||||
required: false
|
||||
type: path
|
||||
version:
|
||||
description:
|
||||
- version to test against helm binary.
|
||||
type: str
|
||||
default: 3.7.0
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: validate helm binary version is lower than 3.5.0
|
||||
helm_test_version:
|
||||
binary_path: path/to/helm
|
||||
version: "3.5.0"
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
message:
|
||||
type: str
|
||||
description: Text message describing the test result.
|
||||
returned: always
|
||||
sample: 'version installed: 3.4.5 is lower than version 3.5.0'
|
||||
result:
|
||||
type: bool
|
||||
description: Test result.
|
||||
returned: always
|
||||
sample: 1
|
||||
"""
|
||||
|
||||
import re
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
|
||||
LooseVersion,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type="path"), version=dict(type="str", default="3.7.0"),
|
||||
),
|
||||
)
|
||||
|
||||
bin_path = module.params.get("binary_path")
|
||||
version = module.params.get("version")
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = "helm"
|
||||
|
||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||
rc, out, err = module.run_command([helm_cmd_common, "version"])
|
||||
if rc != 0:
|
||||
module.fail_json(msg="helm version failed.", err=err, out=out, rc=rc)
|
||||
|
||||
m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
|
||||
installed_version = m.group(1)
|
||||
|
||||
message = "version installed: %s" % installed_version
|
||||
if LooseVersion(installed_version) < LooseVersion(version):
|
||||
message += " is lower than version %s" % version
|
||||
module.exit_json(changed=False, result=True, message=message)
|
||||
else:
|
||||
message += " is greater than version %s" % version
|
||||
module.exit_json(changed=False, result=False, message=message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
tests/integration/targets/helm/meta/main.yml
Normal file
5
tests/integration/targets/helm/meta/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
collections:
|
||||
- kubernetes.core
|
||||
dependencies:
|
||||
- remove_namespace
|
||||
@@ -6,7 +6,7 @@
|
||||
- name: Create namespace
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: "{{ helm_namespace }}"
|
||||
name: "{{ test_namespace[4] }}"
|
||||
|
||||
- name: Copy test chart
|
||||
copy:
|
||||
@@ -15,8 +15,9 @@
|
||||
|
||||
- name: Install chart while skipping CRDs
|
||||
helm:
|
||||
binary_path: "{{ helm_binary }}"
|
||||
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
||||
namespace: "{{ helm_namespace }}"
|
||||
namespace: "{{ test_namespace[4] }}"
|
||||
name: test-crds
|
||||
skip_crds: true
|
||||
register: install
|
||||
@@ -29,10 +30,10 @@
|
||||
- name: Fail to create custom resource
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: ansible.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
namespace: "{{ helm_namespace }}"
|
||||
namespace: "{{ test_namespace[4] }}"
|
||||
name: test-foo
|
||||
foobar: footest
|
||||
ignore_errors: true
|
||||
@@ -41,28 +42,30 @@
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- "result.msg.startswith('Failed to find exact match for example.com/v1.Foo')"
|
||||
- "result.msg.startswith('Failed to find exact match for ansible.com/v1.Foo')"
|
||||
|
||||
# Helm won't install CRDs into an existing release, so we need to delete this, first
|
||||
- name: Uninstall chart
|
||||
helm:
|
||||
namespace: "{{ helm_namespace }}"
|
||||
binary_path: "{{ helm_binary }}"
|
||||
namespace: "{{ test_namespace[4] }}"
|
||||
name: test-crds
|
||||
state: absent
|
||||
|
||||
- name: Install chart with CRDs
|
||||
helm:
|
||||
binary_path: "{{ helm_binary }}"
|
||||
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
|
||||
namespace: "{{ helm_namespace }}"
|
||||
namespace: "{{ test_namespace[4] }}"
|
||||
name: test-crds
|
||||
|
||||
- name: Create custom resource
|
||||
k8s:
|
||||
definition:
|
||||
apiVersion: example.com/v1
|
||||
apiVersion: ansible.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
namespace: "{{ helm_namespace }}"
|
||||
namespace: "{{ test_namespace[4] }}"
|
||||
name: test-foo
|
||||
foobar: footest
|
||||
register: result
|
||||
@@ -82,16 +85,14 @@
|
||||
- name: Remove namespace
|
||||
k8s:
|
||||
kind: Namespace
|
||||
name: "{{ helm_namespace }}"
|
||||
name: "{{ test_namespace[4] }}"
|
||||
state: absent
|
||||
wait: true
|
||||
wait_timeout: 180
|
||||
ignore_errors: true
|
||||
|
||||
# CRDs aren't deleted with a namespace, so we need to manually delete it
|
||||
- name: Remove CRD
|
||||
k8s:
|
||||
kind: CustomResourceDefinition
|
||||
name: foos.example.com
|
||||
name: foos.ansible.com
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user