Compare commits

47 Commits
2.1.1 ... 2.2.2

Author SHA1 Message Date
Mike Graves
d8538ffed3 Release 2.2.2 (#305)
Release 2.2.2

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
2021-12-07 20:08:22 +00:00
Mike Graves
bc168a5727 Remove binary file from molecule test suite (#298) (#302)
[backport/2.2] Remove binary file from molecule test suite

SUMMARY

The binary file used to test k8s_cp is causing larger problems
downstream. There's no reason why the binary file needs to function as
all we care about is that the content of the file has not changed during
the copy process. This can be accomplished by comparing file hashes.

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION


Reviewed-by: Abhijeet Kasurde 
Reviewed-by: None 
(cherry picked from commit fa65698)

Reviewed-by: None <None>
2021-12-07 08:39:20 +00:00
Mike Graves
dc5a1e6dd1 turn network-ee-sanity-tests non-voting (#284) (#303)
[backport/2.2] turn network-ee-sanity-tests non-voting

SUMMARY

turn network-ee-sanity-tests non-voting
Reviewed-by: Mike Graves mgraves@redhat.com
Reviewed-by: None 
(cherry picked from commit b0f1501)

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
2021-12-06 18:49:59 +00:00
Mike Graves
72536fe286 Fix sanity tests (#283) (#304)
[backport/2.2] Fix sanity tests

SUMMARY

Fix sanity tests
Reviewed-by: None 
Reviewed-by: None 
(cherry picked from commit 1116056)

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
2021-12-06 17:13:18 +00:00
Mike Graves
f2d899b939 Release 2.2.1 (#267)
Release 2.2.1

Release 2.2.1

Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
2021-10-19 14:10:21 +00:00
Mike Graves
bc391218a4 common/_wait_for: ensure label_selectors is optional (#239) (#256)
[backport/2.2] common/_wait_for: ensure label_selectors is optional (#239)

Depends-On: ansible/ansible-zuul-jobs#1170
Depends-On: ansible/ansible-zuul-jobs#1169
Depends-On: ansible/ansible-zuul-jobs#1171
common/_wait_for: ensure label_selectors is optional
The label_selectors is a new parameter for _wait_for that was
introduced in #158.
The value is new and it can be set to None to make it optional. It should
not be mandatory a non optional parameter.
Reviewed-by: None 
Reviewed-by: Alina Buzachis 
Reviewed-by: None 
(cherry picked from commit 938f7e1)

Reviewed-by: None <None>
Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
2021-10-15 18:29:21 +00:00
Mike Graves
83b3a1aa39 Use yaml.safe_load in unit tests (#265) (#266)
[backport/2.2] Use yaml.safe_load in unit tests (#265)

Use yaml.safe_load in unit tests
SUMMARY
The function signature in pyyaml 6 for yaml.load changed. Using
safe_load fixes this.
ISSUE TYPE
Bugfix Pull Request
COMPONENT NAME
ADDITIONAL INFORMATION
Reviewed-by: Jill R 
Reviewed-by: None 
Reviewed-by: Gonéri Le Bouder goneri@lebouder.net
(cherry picked from commit 281ff56)

Reviewed-by: None <None>
Reviewed-by: None <None>
2021-10-15 13:02:29 +00:00
Mike Graves
aa41055503 Fix sanity test - devel drops support for python 2.6 (#251) (#258)
Fix sanity test - devel drops support for python 2.6

SUMMARY

ISSUE TYPE

Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
(cherry picked from commit 8436ad1341)

Co-authored-by: abikouo <79859644+abikouo@users.noreply.github.com>
2021-10-14 10:11:05 -04:00
Mike Graves
256fa58ca8 Remove molecule dependencies (#261) (#263)
Remove molecule dependencies

SUMMARY

Depends-on: ansible-collections/cloud.common#92
Molecule is overwriting the cloud.common dependency installed by zuul,
which is causing issues with the CI job for turbo mode. We still need to
find a way to test against the latest released version of cloud.common.

ISSUE TYPE

Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
(cherry picked from commit ff43353de6)
2021-10-14 08:39:16 -04:00
Mike Graves
70db517265 Copy ignore-2.12.txt to ignore-2.13.txt (#247) (#257)
Copy ignore-2.12.txt to ignore-2.13.txt

SUMMARY
Relates to ansible-collections/overview#45 (comment)

Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
(cherry picked from commit 45ba8b1a0d)

Co-authored-by: Andrew Klychkov <aklychko@redhat.com>
2021-10-08 14:09:11 -04:00
Mike Graves
a27c701afe Release version 2.2.0 (#234)
Release version 2.2.0

SUMMARY


ISSUE TYPE


Bugfix Pull Request
Docs Pull Request
Feature Pull Request
New Module Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Jill R <None>
Reviewed-by: None <None>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
2021-09-16 12:28:33 +00:00
Mike Graves
a48a68ecfd Add deprecation notice to k8s_exec (#233)
Add deprecation notice to k8s_exec

SUMMARY


ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
2021-09-14 14:40:58 +00:00
itaru2622
db11675622 fix k8s_cp uploading when target container's WORKDIR is other than '/' (#223)
fix k8s_cp uploading when deployed container's WORKDIR is other than '/'

SUMMARY


fix #222
ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

k8s_cp
ADDITIONAL INFORMATION

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
Reviewed-by: None <None>
2021-09-13 16:51:43 +00:00
itaru2622
07ac24e42e fix k8s_exec, returning rc attribute to follow ansible's common return values. (#230)
fix k8s_exec, returning rc attribute to follow ansible's common return values.

SUMMARY

fix #229.

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

k8s_exec
ADDITIONAL INFORMATION

Reviewed-by: None <None>
Reviewed-by: None <None>
2021-09-13 10:21:24 +00:00
abikouo
63b84d7f54 kustomize lookup plugin (#225)
kustomize lookup plugin

SUMMARY

new lookup plugin to support kustomize feature for kubernetes

ISSUE TYPE


Feature Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: None <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-09-10 21:02:17 +00:00
Mike Graves
b397439972 Fix resource cache not being used (#228)
Fix resource cache not being used

SUMMARY

This was some bad copy/paste from the openshift client. The resource
cache was never being used resulting in unnecessary HTTP requests.

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
2021-09-09 15:26:58 +00:00
Alina Buzachis
8bb455afb9 Minor doc fix turbo mode (#227)
Minor doc fix turbo mode

SUMMARY

Minor doc fix

ISSUE TYPE


Docs Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-09-08 15:23:03 +00:00
Mike Graves
ddbc161121 Add turbo mode docs (#221)
Add turbo mode docs

SUMMARY

Add turbo mode docs

ISSUE TYPE


Docs Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: None <None>
2021-09-02 19:58:43 +00:00
Mike Graves
1da4ef1d53 Disable turbo mode for the validate tests (#218)
Disable turbo mode for the validate tests

Depends-On: ansible/ansible-zuul-jobs#1074
Switching virtualenvs in the test suite does not work so well with turbo
mode enabled because the module may not be executed by the virtualenv
that is specified for that task.

Reviewed-by: None <None>
Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
2021-09-01 07:42:56 +00:00
abikouo
d78b64d792 add support for in-memory kubeconfig (#212)
add support for in-memory kubeconfig

SUMMARY

k8s module support now authentication with kubeconfig parameter as file and dict.

Closes #139
ISSUE TYPE


Feature Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-08-30 09:31:07 +00:00
abikouo
e21ad0212d fix drain test for ansible 2.9 release (#211)
Test molecule on ansible release 2.9 and 2.10

SUMMARY

Debug only

ISSUE TYPE


Feature Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
2021-08-26 16:08:48 +00:00
Mike Graves
47d149f774 Move integration tests to molecule (#198)
Move integration tests to molecule

SUMMARY

There are only a handful of integration tests in tests/integration.
These are presumably left over from before the collection used molecule.
The only integration tests that aren't already covered by molecule are
the ones testing kubernetes-validate. I have moved these tests into
molecule so we can delete the integration test job.

ISSUE TYPE

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
Reviewed-by: None <None>
2021-08-23 16:19:46 +00:00
Mike Graves
77775f25a7 Re-enable support for turbo mode (#169)
Re-enable support for turbo mode

SUMMARY

This re-enables the ability to add turbo mode. It also adds a few more
tests to cover some cases that had been broken in turbo mode previously.
Testing with turbo mode is not currently enabled, and would fail until ansible-collections/cloud.common#69 can be merged and a new cloud.common release is done. This also does not add cloud.common to the collection dependencies until a decision has been made about how enabling/disabling turbo mode will work when cloud.common is already installed.

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
Reviewed-by: None <None>
2021-08-20 15:49:49 +00:00
Abhijeet Kasurde
688cff4ea8 k8s: document multi template feature (#209)
k8s: document multi template feature

SUMMARY
template parameter allows user to provide multiple
template files.
Fixes: #207
Signed-off-by: Abhijeet Kasurde akasurde@redhat.com
ISSUE TYPE

Docs Pull Request

COMPONENT NAME
plugins/modules/k8s.py

Reviewed-by: None <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-08-19 14:52:46 +00:00
abikouo
a4701a6806 replace iterator by generator (#205)
Fix network_sanity_ee_tests

SUMMARY

Network sanity ee tests are broken

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: None <None>
2021-08-17 13:33:27 +00:00
abikouo
b875531c8a k8s_drain new module (#141)
k8s_drain new module

SUMMARY

new module to drain, cordon or uncordon node from k8s cluster.
#141

ISSUE TYPE


New Module Pull Request

COMPONENT NAME

k8s_drain

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: None <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-08-09 08:00:34 +00:00
abikouo
76a77aff1f [connection plugin] add missing information from censored command (#196)
[connection plugin] add missing information from censored command

SUMMARY
when running playbook with vvv option, the censored command display is not reflecting the execution
ISSUE TYPE


Bugfix Pull Request

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-08-04 13:56:27 +00:00
Joshua K
25590804cb Add support for waiting on a StatefulSet. (#195)
Add support for waiting on a StatefulSet.

SUMMARY
This PR implements support for waiting on StatefulSet for readiness similar to how the other waiters currently work.
ISSUE TYPE

Feature Pull Request

ADDITIONAL INFORMATION
This was designed to (mostly) mimic the behaviour of the StatefulSetStatusViewer used by kubectl rollout status -w.

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Joshua K <None>
Reviewed-by: None <None>
Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-07-30 14:41:45 +00:00
Gonéri Le Bouder
7fbfc985ab makefile: simplify how we build PYTHON_VERSION (#194)
makefile: simplify how we build PYTHON_VERSION

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-07-29 18:39:35 +00:00
abikouo
4b682666f1 k8s - add label_selectors options (#158)
k8s - add label_selectors options

SUMMARY
k8s now support label_selectors options same as k8s_info

Resolves #43

ISSUE TYPE


Feature Pull Request


COMPONENT NAME

k8s

Reviewed-by: Mike Graves <mgraves@redhat.com>
Reviewed-by: None <None>
2021-07-29 09:56:34 +00:00
Gonéri Le Bouder
abd2abb33e README: repo -> repository (#188)
README: repo -> repository

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Gonéri Le Bouder <goneri@lebouder.net>
2021-07-28 16:35:28 +00:00
abikouo
f5a81941ff tox ini configuration (#181)
* tox for zuul jobs

* Delete .flake8

* linters

* Update tox.ini

* integration

* integration
2021-07-27 18:16:52 +02:00
Abhijeet Kasurde
fa819d3f11 test molecule zuul job (#180) 2021-07-27 15:48:37 +05:30
abikouo
f98469e5ef ansible sanity for k8s_cp (#183) 2021-07-27 09:30:24 +05:30
abikouo
c330c7ec65 k8s_cp - a new module for copying files to/from a Pod (#127)
* k8s_cp module

* add documentation for k8s_cp module

* add doc for the new module

* pods should be running

* support for binary, archive and zip file

* sanity

* Delete file.txt

* remove unused

* set back

* Update collection.txt

* Update test_copy_errors.yml

* Update plugins/modules/k8s_cp.py

Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>

* Update k8s_cp.py

* Update k8s_cp.py

* tar binary requirements

* Update common.py

* Update k8s_cp.py

* Update k8s_cp.py

* replace kind with binary file

* Update test_copy_large_file.yml

* Update plugins/action/k8s_info.py

Co-authored-by: Mike Graves <mgraves@redhat.com>

* Update k8s_info.py

* Update k8s_info.py

* Update k8s_cp.py

Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Mike Graves <mgraves@redhat.com>
2021-07-26 13:21:34 +02:00
Abhijeet Kasurde
2f59c3db77 Add exception for import sanity (#177)
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2021-07-21 15:20:25 +02:00
Abhijeet Kasurde
2076da7dc0 Update test-requirements for Zuul job (#178)
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2021-07-21 15:20:15 +02:00
abikouo
3c36b6fa0f k8s support diff mode (#146)
* support diff mode for k8s module

* Update and rename 145-k8s-add-support-diff-mode.yml to 146-k8s-add-support-diff-mode.yml

* Update 146-k8s-add-support-diff-mode.yml

* Update changelogs/fragments/146-k8s-add-support-diff-mode.yml

Co-authored-by: Mike Graves <mgraves@redhat.com>

* update k8s_scale and k8s_json_patch

* diff for k8s_scale  and k8s_json_patch

Co-authored-by: Mike Graves <mgraves@redhat.com>
2021-07-21 14:29:28 +02:00
Abhijeet Kasurde
e9be88f212 Add script to handle sanity ignore (#173)
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2021-07-20 15:27:43 +05:30
Vincent Van Ouytsel
a0a6d7121f helm: add support for history-max parameter (#164)
* helm: add support for history-max parameter

The --history-max parameter allows the user to set a maximum amount of
revisions per release to be kept in the history.

By default helm keeps 10 revisions per release, which means that 10
secrets will be kept per release.

* helm: remove default for history_max

When the history_max option is not set, the module will not pass
'--history-max' to the CLI command. This ensures that the defaults of
the helm CLI will alwasy be used.

* helm: remove whitespace trail

* helm: add mutually exclusive logic

The 'history_max' parameter is not available when using the 'helm
install' command, it is only implemented for 'helm ugprade'.

The 'replace' option uses the 'install' parameter, thus 'replace' and
'history_max' have to be mutually exclusive.

* helm: formatting changes
2021-07-15 13:21:22 -04:00
stg
2e98493010 Add sinceSeconds parameter to k8s_logs (#142)
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Mike Graves <mgraves@redhat.com>
2021-07-15 13:23:32 +05:30
Abhijeet Kasurde
5fb3ecbb50 common: import k8sdynamicclient directly (#163) 2021-07-09 10:02:35 +05:30
Abhijeet Kasurde
eab3aa29bf Docs: Migrate Scenario Guide to collection (#143)
Migrate scenario guide from ansible/ansible repo to
this collection.

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2021-07-09 09:55:47 +05:30
Abhijeet Kasurde
04f227e9b9 lookup: Recommend query instead of lookup (#155) 2021-07-08 22:26:03 +05:30
abikouo
25100e7f5e adding a prepare step to validate that node is ready for schedule (#160) 2021-07-07 07:23:45 -04:00
abikouo
ccc2b61719 multiple fixes on molecule helm testing (#121) 2021-07-01 14:36:39 +02:00
Mike Graves
8c7b302916 Support template param in other collections (#154)
* Support template param in other collections

The action plugin for k8s does a collection name check for the template
param, but it's missing redhat.openshift and community.kubernetes.

* Add changelog fragment

* Fix test
2021-06-29 08:41:33 -04:00
102 changed files with 7430 additions and 298 deletions

View File

@@ -1,141 +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']
ansible_version: ['==2.9.*', '==2.10.*', '']
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${{ matrix.ansible_version }} "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

3
.gitignore vendored
View File

@@ -13,3 +13,6 @@ changelogs/.plugin-cache.yaml
tests/output
tests/integration/cloud-config-*
.cache
# Helm charts
molecule/default/*-chart-*.tgz

View File

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

View 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

View File

@@ -5,6 +5,63 @@ Kubernetes Collection Release Notes
.. contents:: Topics
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
======

View File

@@ -1,8 +1,8 @@
# Also needs to be updated in galaxy.yml
VERSION = 2.1.1
VERSION = 2.2.2
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

View File

@@ -2,7 +2,7 @@
[![CI](https://github.com/ansible-collections/kubernetes.core/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/kubernetes.core/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/kubernetes.core)](https://codecov.io/gh/ansible-collections/kubernetes.core)
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.1.1
version: 2.2.2
```
### Installing the Kubernetes Python Library
@@ -159,6 +162,22 @@ If upgrading older playbooks which were built prior to Ansible 2.10 and this col
For documentation on how to use individual modules and other content included in this collection, please see the links in the 'Included content' section earlier in this README.
## Ansible Turbo mode Tech Preview
The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode, 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
If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there.

View File

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

View File

@@ -15,7 +15,7 @@ Following document provides overview of Ansible Turbo mode in ``kubernetes.core`
Synopsis
--------
- A brief introduction about Ansible Turbo mode in ``kuberentes.core`` collection.
- Ansible Turbo mode is an optional performance optimization. It can be enabled by 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.

View File

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

View 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>`_.

View 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

View 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

View 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

View 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

View File

@@ -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>

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View 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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>local_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
</div>
</td>
<td>
</td>
<td>
<div>Path of the local file or directory.</div>
<div>Required when <em>state</em> is set to <code>from_pod</code>.</div>
<div>Mutually exclusive with <em>content</em>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>namespace</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>The pod namespace name.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>no_preserve</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>The copied file/directory&#x27;s ownership and permissions will not be preserved in the container.</div>
<div>This option is ignored when <em>content</em> is set or when <em>state</em> is set to <code>from_pod</code>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
<div>Please read the description of the <code>username</code> option for a discussion of when this option is applicable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>persist_config</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Whether or not to save the kube config refresh tokens. Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.</div>
<div>When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the new refresh token to the kube config file.</div>
<div>Default to false.</div>
<div>Please note that the current version of the k8s python client library does not support setting this flag to True yet.</div>
<div>The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>pod</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>The pod name.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.</div>
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy_headers</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">dictionary</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
</td>
<td>
</td>
<td>
<div>The Header used for the HTTP proxy.</div>
<div>Documentation can be found here <a href='https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers'>https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers</a>.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>basic_auth</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Colon-separated username:password for basic authentication header.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy_basic_auth</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Colon-separated username:password for proxy basic authentication header.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>user_agent</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>String representing the user-agent you want, such as foo/1.0.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>remote_path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">path</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>Path of the file or directory to copy.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>state</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>to_pod</b>&nbsp;&larr;</div></li>
<li>from_pod</li>
</ul>
</td>
<td>
<div>When set to <code>to_pod</code>, the local <em>local_path</em> file or directory will be copied to <em>remote_path</em> into the pod.</div>
<div>When set to <code>from_pod</code>, the remote file or directory <em>remote_path</em> from pod will be copied locally to <em>local_path</em>.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>username</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
<div>Please note that this only works with clusters configured to use HTTP Basic Auth. If your cluster has a different form of authentication (e.g. OAuth2 in OpenShift), this option will not work as expected and you should look into the <span class='module'>community.okd.k8s_auth</span> module, as that might do what you need.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>validate_certs</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
</td>
</tr>
</table>
<br/>
Notes
-----
.. note::
- the tar binary is required on the container when copying from local filesystem to pod.
- To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
Examples
--------
.. code-block:: yaml
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar
local_path: /tmp/foo
# kubectl cp /tmp/foo_dir some-namespace/some-pod:/tmp/bar_dir
- name: Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar_dir
local_path: /tmp/foo_dir
# kubectl cp /tmp/foo some-namespace/some-pod:/tmp/bar -c some-container
- name: Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
container: some-container
remote_path: /tmp/bar
local_path: /tmp/foo
no_preserve: True
state: to_pod
# kubectl cp some-namespace/some-pod:/tmp/foo /tmp/bar
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo
local_path: /tmp/bar
state: from_pod
# copy content into a file in the remote pod
- name: Copy /tmp/foo from a remote pod to /tmp/bar locally
kubernetes.core.k8s_cp:
state: to_pod
namespace: some-namespace
pod: some-pod
remote_path: /tmp/foo.txt
content: "This content will be copied into remote file"
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>result</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>success</td>
<td>
<div>message describing the copy operation successfully done.</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)

View File

@@ -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>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Forces drain to use delete rather than evict.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>force</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>ignore_daemonsets</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Ignore DaemonSet-managed pods.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>terminate_grace_period</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify how many seconds to wait before forcefully terminating.</div>
<div>If not specified, the default grace period for the object type will be used.</div>
<div>The value zero indicates delete immediately.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait_sleep</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue">5</div>
</td>
<td>
<div>Number of seconds to sleep between checks.</div>
<div>Ignored if <code>wait_timeout</code> is not set.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wait_timeout</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
</td>
<td>
<div>The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>host</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>kubeconfig</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
</div>
</td>
<td>
</td>
<td>
<div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
<div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>name</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
<div>The name of the node.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
<div>Please read the description of the <code>username</code> option for a discussion of when this option is applicable.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>persist_config</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Whether or not to save the kube config refresh tokens. Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.</div>
<div>When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the new refresh token to the kube config file.</div>
<div>Default to false.</div>
<div>Please note that the current version of the k8s python client library does not support setting this flag to True yet.</div>
<div>The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.</div>
<div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy_headers</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">dictionary</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
</td>
<td>
</td>
<td>
<div>The Header used for the HTTP proxy.</div>
<div>Documentation can be found here <a href='https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers'>https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers</a>.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>basic_auth</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Colon-separated username:password for basic authentication header.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>proxy_basic_auth</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Colon-separated username:password for proxy basic authentication header.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>user_agent</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>String representing the user-agent you want, such as foo/1.0.</div>
<div>Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>state</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>cordon</li>
<li><div style="color: blue"><b>drain</b>&nbsp;&larr;</div></li>
<li>uncordon</li>
</ul>
</td>
<td>
<div>Determines whether to drain, cordon, or uncordon node.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>username</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
<div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
<div>Please note that this only works with clusters configured to use HTTP Basic Auth. If your cluster has a different form of authentication (e.g. OAuth2 in OpenShift), this option will not work as expected and you should look into the <span class='module'>community.okd.k8s_auth</span> module, as that might do what you need.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>validate_certs</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
</td>
</tr>
</table>
<br/>
Notes
-----
.. note::
- To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
Examples
--------
.. code-block:: yaml
- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
kubernetes.core.k8s_drain:
state: drain
name: foo
force: yes
- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
kubernetes.core.k8s_drain:
state: drain
name: foo
delete_options:
terminate_grace_period: 900
- name: Mark node "foo" as schedulable.
kubernetes.core.k8s_drain:
state: uncordon
name: foo
- name: Mark node "foo" as unschedulable.
kubernetes.core.k8s_drain:
state: cordon
name: foo
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>result</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>success</td>
<td>
<div>The node status and the number of pods deleted.</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo (@abikouo)

View File

@@ -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 &gt;= 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">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>rc</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
</td>
<td></td>
<td>
<div>The command status code</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>return_code</b>
@@ -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>

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View File

@@ -18,7 +18,7 @@ Version added: 2.0.0
Synopsis
--------
- This module is used to apply RFC 6902 JSON patch operations only.
- Use the :ref:`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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View File

@@ -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 &gt;= 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

View File

@@ -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') }}"

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>label_selectors</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">list</span>
/ <span style="color: purple">elements=string</span>
</div>
<div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
</td>
<td>
</td>
<td>
<div>Selector (label query) to filter on.</div>
</td>
</tr>
<tr>
@@ -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&#x27;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

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View File

@@ -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 &gt;= 17.17.0. Added in version 2.2.0.</div>
</td>
</tr>
<tr>

View 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 &#x27;kustomization.yaml&#x27;, or a git repository URL with a path suffix specifying same with respect to the repository root.</div>
<div>If omitted, &#x27;.&#x27; is assumed.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>opt_dirs</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>An optional list of directories to search for the executable in addition to PATH.</div>
</td>
</tr>
</table>
<br/>
Notes
-----
.. note::
- If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
Examples
--------
.. code-block:: yaml
- name: Run lookup using kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
- name: Run lookup using kubectl kustomize
set_fact:
resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
- name: Create kubernetes resources for lookup output
k8s:
definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this lookup:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="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">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>api_version</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>success</td>
<td>
<div>The versioned schema of this representation of an object.</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>kind</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>success</td>
<td>
<div>Represents the REST resource this object represents.</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>metadata</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">complex</span>
</div>
</td>
<td>success</td>
<td>
<div>Standard object metadata. Includes name, namespace, annotations, labels, etc.</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>spec</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">complex</span>
</div>
</td>
<td>success</td>
<td>
<div>Specific attributes of the object. Will vary based on the <em>api_version</em> and <em>kind</em>.</div>
<br/>
</td>
</tr>
<tr>
<td class="elbow-placeholder">&nbsp;</td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>status</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">complex</span>
</div>
</td>
<td>success</td>
<td>
<div>Current status details for the object.</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
Status
------
Authors
~~~~~~~
- Aubin Bikouo <@abikouo>
.. hint::
Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up.

View File

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

View File

@@ -14,6 +14,8 @@ action_groups:
- k8s_log
- k8s_scale
- k8s_service
- k8s_cp
- k8s_drain
plugin_routing:
action:
@@ -31,6 +33,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:

View File

@@ -132,6 +132,15 @@
tags: [ template, k8s ]
tags:
- always
- name: Include validate.yml
include_tasks:
file: tasks/validate.yml
apply:
tags: [ validate, k8s ]
tags:
- always
- name: Include waiter.yml
include_tasks:
file: tasks/waiter.yml
@@ -160,7 +169,31 @@
include_tasks:
file: tasks/lookup_k8s.yml
apply:
tags: [ lookup, k8s ]
tags: [ lookup_k8s, k8s ]
tags:
- always
- name: Include label_selectors.yml
include_tasks:
file: tasks/label_selectors.yml
apply:
tags: [ label_selectors, k8s ]
tags:
- always
- name: Include diff.yml
include_tasks:
file: tasks/diff.yml
apply:
tags: [ diff, k8s ]
tags:
- always
- name: Include lookup_kustomize.yml
include_tasks:
file: tasks/lookup_kustomize.yml
apply:
tags: [ lookup_kustomize, k8s ]
tags:
- always
@@ -169,6 +202,11 @@
tags:
- helm
- role: k8scopy
tags:
- copy
- k8s
post_tasks:
- name: Ensure namespace exists
k8s:

View File

@@ -6,10 +6,6 @@ driver:
login_cmd_template: 'docker exec -ti {instance} bash'
ansible_connection_options:
ansible_connection: docker
lint: |
set -e
yamllint .
flake8
platforms:
- name: instance-kind
provisioner:
@@ -17,7 +13,7 @@ provisioner:
log: true
config_options:
inventory:
enable_plugins: kubernetes.core.k8s
enable_plugins: kubernetes.core.k8s,yaml
lint: {}
inventory:
hosts:
@@ -33,11 +29,7 @@ scenario:
name: default
test_sequence:
- dependency
- lint
- syntax
- prepare
- converge
- verify
dependency:
name: galaxy
options:
requirements-file: requirements.yml

View File

@@ -0,0 +1,12 @@
---
- name: Prepare
hosts: localhost
connection: local
collections:
- kubernetes.core
tasks:
- name: Include drain.yml
include_tasks:
file: tasks/drain.yml

View File

@@ -0,0 +1,3 @@
---
collections:
- kubernetes.core

View File

@@ -15,6 +15,7 @@
- name: Install chart while skipping CRDs
helm:
binary_path: "{{ helm_binary }}"
chart_ref: "/tmp/helm_test_crds/{{ test_chart }}"
namespace: "{{ helm_namespace }}"
name: test-crds
@@ -46,12 +47,14 @@
# Helm won't install CRDs into an existing release, so we need to delete this, first
- name: Uninstall chart
helm:
binary_path: "{{ helm_binary }}"
namespace: "{{ helm_namespace }}"
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 }}"
name: test-crds

View File

@@ -1,6 +1,7 @@
---
- name: Add chart repo
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm
repo_url: "{{ chart_test_repo }}"
@@ -14,6 +15,7 @@
- name: Add chart repo
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm
repo_url: "{{ chart_test_repo }}"
state: absent

View File

@@ -6,6 +6,7 @@
block:
- name: Install helm diff
helm_plugin:
binary_path: "{{ helm_binary }}"
state: present
plugin_path: https://github.com/databus23/helm-diff
@@ -136,6 +137,7 @@
- name: Uninstall helm diff
helm_plugin:
binary_path: "{{ helm_binary }}"
state: absent
plugin_name: diff
ignore_errors: yes

View File

@@ -97,6 +97,7 @@
- name: Gather Helm plugin info
helm_plugin_info:
binary_path: "{{ helm_binary }}"
register: r
- name: Set sample_plugin version

View File

@@ -1,11 +1,13 @@
---
- name: "Ensure test_helm_repo doesn't exist"
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
state: absent
- name: Add test_helm_repo chart repository
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
repo_url: "{{ chart_test_repo }}"
register: repository
@@ -17,6 +19,7 @@
- name: Check idempotency
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
repo_url: "{{ chart_test_repo }}"
register: repository
@@ -28,6 +31,7 @@
- name: Failed to add repository with the same name
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
repo_url: "https://other-charts.url"
register: repository_errors
@@ -40,6 +44,7 @@
- name: Remove test_helm_repo chart repository
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
state: absent
register: repository
@@ -51,6 +56,7 @@
- name: Check idempotency after remove
helm_repository:
binary_path: "{{ helm_binary }}"
name: test_helm_repo
state: absent
register: repository

View File

@@ -0,0 +1,15 @@
---
# defaults file for k8copy
copy_namespace: copy
pod_with_one_container:
name: pod-copy-0
container: container-00
pod_with_two_container:
name: pod-copy-1
container:
- container-10
- container-11
kubectl_path: /tmp/kubectl

Binary file not shown.

View File

@@ -0,0 +1 @@
kubernetes.core

View File

@@ -0,0 +1 @@
k8s_cp

View File

@@ -0,0 +1 @@
This is a simple file used to test k8s_cp module on ansible.

View File

@@ -0,0 +1,2 @@
cloud team
content team

View File

@@ -0,0 +1 @@
This content will be copied into remote Pod.

View File

@@ -0,0 +1,91 @@
#!/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_diff
short_description: Create large file with a defined size.
author:
- Aubin Bikouo (@abikouo)
description:
- This module is used to validate k8s_cp module.
options:
path:
description:
- The destination path for the file to create.
type: path
required: yes
size:
description:
- The size of the output file in MB.
type: int
default: 400
binary:
description:
- If this flag is set to yes, the generated file content binary data.
type: bool
default: False
'''
EXAMPLES = r'''
- name: create 150MB file
k8s_diff:
path: large_file.txt
size: 150
'''
RETURN = r'''
'''
import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
def execute_module(module):
try:
size = module.params.get('size') * 1024 * 1024
path = module.params.get('path')
write_mode = "w"
if module.params.get('binary'):
content = os.urandom(size)
write_mode = "wb"
else:
content = ""
count = 0
while len(content) < size:
content += "This file has been generated using ansible: {0}\n".format(count)
count += 1
with open(path, write_mode) as f:
f.write(content)
module.exit_json(changed=True, size=len(content))
except Exception as e:
module.fail_json(msg="failed to create file due to: {0}".format(to_native(e)))
def main():
argument_spec = {}
argument_spec['size'] = {'type': 'int', 'default': 400}
argument_spec['path'] = {'type': 'path', 'required': True}
argument_spec['binary'] = {'type': 'bool', 'default': False}
module = AnsibleModule(argument_spec=argument_spec)
execute_module(module)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,217 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Aubin Bikouo <@abikouo>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
module: kubectl_file_compare
short_description: Compare file and directory using kubectl
author:
- Aubin Bikouo (@abikouo)
description:
- This module is used to validate k8s_cp module.
- Compare the local file/directory with the remote pod version
notes:
- This module authenticates on kubernetes cluster using default kubeconfig only.
options:
namespace:
description:
- The pod namespace name
type: str
required: yes
pod:
description:
- The pod name
type: str
required: yes
container:
description:
- The container to retrieve files from.
type: str
remote_path:
description:
- Path of the file or directory on Pod.
type: path
required: yes
local_path:
description:
- Path of the local file or directory.
type: path
content:
description:
- local content to compare with remote file from pod.
- mutually exclusive with option I(local_path).
type: path
required: yes
args:
description:
- The file is considered to be an executable.
- The tool will be run locally and on pod and compare result from output and stderr.
type: list
kubectl_path:
description:
- Path to the kubectl executable, if not specified it will be download.
type: path
'''
EXAMPLES = r'''
- name: compare local /tmp/foo with /tmp/bar in a remote pod
kubectl_file_compare:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/bar
local_path: /tmp/foo
kubectl_path: /tmp/test/kubectl
- name: Compare executable running help command
kubectl_file_compare:
namespace: some-namespace
pod: some-pod
remote_path: /tmp/test/kubectl
local_path: kubectl
kubectl_path: /tmp/test/kubectl
args:
- "--help"
'''
RETURN = r'''
'''
import os
import filecmp
from tempfile import NamedTemporaryFile, TemporaryDirectory
from ansible.module_utils.basic import AnsibleModule
def kubectl_get_content(module, dest_dir):
kubectl_path = module.params.get('kubectl_path')
if kubectl_path is None:
kubectl_path = module.get_bin_path('kubectl', required=True)
namespace = module.params.get('namespace')
pod = module.params.get('pod')
file = module.params.get('remote_path')
cmd = [
kubectl_path,
'cp',
"{0}/{1}:{2}".format(namespace, pod, file)
]
container = module.params.get('container')
if container:
cmd += ['-c', container]
local_file = os.path.join(dest_dir, os.path.basename(module.params.get('remote_path')))
cmd.append(local_file)
rc, out, err = module.run_command(cmd)
return local_file, err, rc, out
def kubectl_run_from_pod(module):
kubectl_path = module.params.get('kubectl_path')
if kubectl_path is None:
kubectl_path = module.get_bin_path('kubectl', required=True)
cmd = [
kubectl_path,
'exec',
module.params.get('pod'),
'-n',
module.params.get('namespace')
]
container = module.params.get('container')
if container:
cmd += ['-c', container]
cmd += ['--', module.params.get('remote_path')]
cmd += module.params.get('args')
return module.run_command(cmd)
def compare_directories(dir1, dir2):
test = filecmp.dircmp(dir1, dir2)
if any([len(test.left_only) > 0, len(test.right_only) > 0, len(test.funny_files) > 0]):
return False
(t, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, test.common_files, shallow=False)
if len(mismatch) > 0 or len(errors) > 0:
return False
for common_dir in test.common_dirs:
new_dir1 = os.path.join(dir1, common_dir)
new_dir2 = os.path.join(dir2, common_dir)
if not compare_directories(new_dir1, new_dir2):
return False
return True
def execute_module(module):
args = module.params.get('args')
local_path = module.params.get('local_path')
namespace = module.params.get('namespace')
pod = module.params.get('pod')
file = module.params.get('remote_path')
content = module.params.get('content')
if args:
pod_rc, pod_out, pod_err = kubectl_run_from_pod(module)
rc, out, err = module.run_command([module.params.get('local_path')] + args)
if rc == pod_rc and out == pod_out:
module.exit_json(msg="{0} and {1}/{2}:{3} are same.".format(
local_path, namespace, pod, file
), rc=rc, stderr=err, stdout=out)
result = dict(local=dict(rc=rc, out=out, err=err), remote=dict(rc=pod_rc, out=pod_out, err=pod_err))
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.", **result)
else:
with TemporaryDirectory() as tmpdirname:
file_from_pod, err, rc, out = kubectl_get_content(module=module, dest_dir=tmpdirname)
if not os.path.exists(file_from_pod):
module.fail_json(msg="failed to copy content from pod", error=err, output=out)
if content is not None:
with NamedTemporaryFile(mode="w") as tmp_file:
tmp_file.write(content)
tmp_file.flush()
if filecmp.cmp(file_from_pod, tmp_file.name):
module.exit_json(msg=f"defined content and {namespace}/{pod}:{file} are same.")
module.fail_json(msg=f"defined content and {namespace}/{pod}:{file} are same.")
if os.path.isfile(local_path):
if filecmp.cmp(file_from_pod, local_path):
module.exit_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
if os.path.isdir(local_path):
if compare_directories(file_from_pod, local_path):
module.exit_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
module.fail_json(msg=f"{local_path} and {namespace}/{pod}:{file} are same.")
def main():
argument_spec = {}
argument_spec['namespace'] = {'type': 'str', 'required': True}
argument_spec['pod'] = {'type': 'str', 'required': True}
argument_spec['container'] = {}
argument_spec['remote_path'] = {'type': 'path', 'required': True}
argument_spec['local_path'] = {'type': 'path'}
argument_spec['content'] = {'type': 'str'}
argument_spec['kubectl_path'] = {'type': 'path'}
argument_spec['args'] = {'type': 'list'}
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[('local_path', 'content')],
required_one_of=[['local_path', 'content']])
execute_module(module)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,3 @@
---
collections:
- kubernetes.core

View File

@@ -0,0 +1,46 @@
---
- block:
- name: Download kubeclt executable used to compare results
get_url:
url: https://dl.k8s.io/release/v1.21.3/bin/linux/amd64/kubectl
dest: "{{ kubectl_path }}"
- name: make kubectl executable
ansible.builtin.file:
path: "{{ kubectl_path }}"
mode: "+x"
# Ensure namespace and create pod to perform tests on
- name: Ensure namespace exists
k8s:
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ copy_namespace }}"
- name: Create Pods
k8s:
namespace: '{{ copy_namespace }}'
wait: yes
template: pods_definition.j2
- include_tasks: test_copy_errors.yml
- include_tasks: test_copy_file.yml
- include_tasks: test_multi_container_pod.yml
- include_tasks: test_copy_directory.yml
- include_tasks: test_copy_large_file.yml
always:
- name: Remove kubectl executable
ansible.builtin.file:
path: "{{ kubectl_path }}"
state: absent
ignore_errors: true
- name: Remove namespace
k8s:
kind: Namespace
name: "{{ copy_namespace }}"
state: absent
ignore_errors: true

View File

@@ -0,0 +1,85 @@
---
- block:
- name: copy directory into remote Pod (create new directory)
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /dest_data
local_path: files/data
state: to_pod
- name: compare directories
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /dest_data
local_path: '{{ role_path }}/files/data'
kubectl_path: "{{ kubectl_path }}"
- name: copy directory into remote Pod (existing directory)
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp
local_path: files/data
state: to_pod
- name: compare directories
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/data
local_path: '{{ role_path }}/files/data'
kubectl_path: "{{ kubectl_path }}"
- name: copy directory from Pod into local filesystem (new directory to create)
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/data
local_path: /tmp/test
state: from_pod
- name: compare directories
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/data
local_path: /tmp/test
kubectl_path: "{{ kubectl_path }}"
- name: copy directory from Pod into local filesystem (existing directory)
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/data
local_path: /tmp
state: from_pod
- name: compare directories
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/data
local_path: /tmp/data
kubectl_path: "{{ kubectl_path }}"
always:
- name: Remove directories created into remote Pod
k8s_exec:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
command: 'rm -rf {{ item }}'
ignore_errors: true
with_items:
- /dest_data
- /tmp/data
- name: Remove local directories
file:
path: '{{ item }}'
state: absent
ignore_errors: true
with_items:
- /tmp/data
- /tmp/test

View File

@@ -0,0 +1,69 @@
---
# copy non-existent local file should fail
- name: copy non-existent file into remote Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp
local_path: this_file_does_not_exist
state: to_pod
ignore_errors: true
register: copy_non_existent
- name: check that error message is as expected
assert:
that:
- copy_non_existent is failed
- copy_non_existent.msg == "this_file_does_not_exist does not exist in local filesystem"
# copy non-existent pod file should fail
- name: copy of non-existent file from remote pod should fail
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /this_file_does_not_exist
local_path: /tmp
state: from_pod
ignore_errors: true
register: copy_non_existent
- name: check that error message is as expected
assert:
that:
- copy_non_existent is failed
- copy_non_existent.msg == "/this_file_does_not_exist does not exist in remote pod filesystem"
# copy file into multiple container pod without specifying the container should fail
- name: copy file into multiple container pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /tmp
local_path: files/simple_file.txt
state: to_pod
ignore_errors: true
register: copy_multi_container
- name: check that error message is as expected
assert:
that:
- copy_multi_container is failed
- copy_multi_container.msg == "Pod contains more than 1 container, option 'container' should be set"
# copy using non-existent container from pod should failed
- name: copy file into multiple container pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /tmp
local_path: files/simple_file.txt
state: to_pod
container: this_is_a_fake_container
ignore_errors: true
register: copy_fake_container
- name: check that error message is as expected
assert:
that:
- copy_fake_container is failed
- copy_fake_container.msg == "Pod has no container this_is_a_fake_container"

View File

@@ -0,0 +1,206 @@
---
- block:
# Text file
- name: copy text file into remote pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp
local_path: files/simple_file.txt
state: to_pod
- name: Compare files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_file.txt
content: "{{ lookup('file', 'simple_file.txt')}}"
kubectl_path: "{{ kubectl_path }}"
- name: Copy simple text file from Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_file.txt
local_path: /tmp/copy_from_pod.txt
state: from_pod
- name: Compare files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_file.txt
local_path: /tmp/copy_from_pod.txt
kubectl_path: "{{ kubectl_path }}"
# Binary file
- name: Create temp binary file
tempfile:
state: file
register: binfile
- name: Generate random binary content
command: dd if=/dev/urandom of={{ binfile.path }} bs=1M count=1
- name: Copy executable into Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/hello.exe
local_path: "{{ binfile.path }}"
state: to_pod
- name: Get remote hash
kubernetes.core.k8s_exec:
namespace: "{{ copy_namespace }}"
pod: "{{ pod_with_one_container.name }}"
command: sha256sum -b /tmp/hello.exe
register: remote_hash
- name: Get local hash
command: sha256sum -b {{ binfile.path }}
register: local_hash
- assert:
that:
- remote_hash.stdout.split()[0] == local_hash.stdout.split()[0]
- name: Generate tempfile
tempfile:
state: file
register: binfile
- name: Copy executable from Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/hello.exe
local_path: "{{ binfile.path }}"
state: from_pod
- name: Get remote hash
kubernetes.core.k8s_exec:
namespace: "{{ copy_namespace }}"
pod: "{{ pod_with_one_container.name }}"
command: sha256sum -b /tmp/hello.exe
register: remote_hash
- name: Get local hash
command: sha256sum -b {{ binfile.path }}
register: local_hash
- assert:
that:
- remote_hash.stdout.split()[0] == local_hash.stdout.split()[0]
# zip files
- name: copy zip file into remote pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp
local_path: files/simple_zip_file.txt.gz
state: to_pod
- name: compare zip files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_zip_file.txt.gz
local_path: '{{ role_path }}/files/simple_zip_file.txt.gz'
kubectl_path: "{{ kubectl_path }}"
- name: copy zip file from pod into local filesystem
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_zip_file.txt.gz
local_path: /tmp/copied_from_pod.txt.gz
state: from_pod
- name: compare zip files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/simple_zip_file.txt.gz
local_path: /tmp/copied_from_pod.txt.gz
kubectl_path: "{{ kubectl_path }}"
# tar files
- name: copy archive into remote pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp
local_path: files/archive.tar
state: to_pod
- name: compare archive
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/archive.tar
local_path: '{{ role_path }}/files/archive.tar'
kubectl_path: "{{ kubectl_path }}"
- name: copy archive from remote pod into local filesystem
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/archive.tar
local_path: /tmp/local_archive.tar
state: from_pod
- name: compare archive
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /tmp/archive.tar
local_path: /tmp/local_archive.tar
kubectl_path: "{{ kubectl_path }}"
# Copy into Pod using content option
- name: set content to be copied into Pod
set_fact:
pod_content: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits,punctuation length=128') }}"
- name: copy archive into remote pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /this_content.txt
content: '{{ pod_content }}'
state: to_pod
- name: Assert that content is as expected into Pod
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /this_content.txt
content: '{{ pod_content }}'
kubectl_path: "{{ kubectl_path }}"
always:
- name: Delete file created on Pod
k8s_exec:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
command: 'rm {{ item }}'
ignore_errors: true
with_items:
- /tmp/simple_file.txt
- /tmp/hello.exe
- /tmp/simple_zip_file.txt.gz
- /tmp/archive.tar
- /this_content.txt
- name: Delete file created locally
file:
path: '{{ item }}'
state: absent
with_items:
- /tmp/copy_from_pod.txt
- /tmp/hello
- /tmp/copied_from_pod.txt.gz
- /tmp/local_archive.tar

View File

@@ -0,0 +1,103 @@
---
- name: test copy of large binary and text files
block:
- set_fact:
test_directory: "/tmp/test_k8scp_large_files"
no_log: true
- name: create temporary directory for local files
ansible.builtin.file:
path: "{{ test_directory }}"
state: directory
- name: create large text file
k8s_create_file:
path: "{{ test_directory }}/large_text_file.txt"
size: 150
- name: create large binary file
k8s_create_file:
path: "{{ test_directory }}/large_bin_file.bin"
size: 200
binary: true
# Copy large text file from/to local filesystem to Pod
- name: copy large file into remote Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_text_file.txt
local_path: "{{ test_directory }}/large_text_file.txt"
state: to_pod
- name: Compare files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_text_file.txt
local_path: "{{ test_directory }}/large_text_file.txt"
kubectl_path: "{{ kubectl_path }}"
- name: copy large file from Pod into local filesystem
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_text_file.txt
local_path: "{{ test_directory }}/large_text_file_from_pod.txt"
state: from_pod
- name: Compare files
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_text_file.txt
local_path: "{{ test_directory }}/large_text_file_from_pod.txt"
kubectl_path: "{{ kubectl_path }}"
# Copy large binary file from/to local filesystem to Pod
- name: copy large file into remote Pod
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_bin_file.bin
local_path: "{{ test_directory }}/large_bin_file.bin"
state: to_pod
- name: Compare executable, local vs remote
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_bin_file.bin
local_path: "{{ test_directory }}/large_bin_file.bin"
- name: copy executable from pod into local filesystem
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_bin_file.bin
local_path: "{{ test_directory }}/large_bin_file_from_pod.bin"
state: from_pod
- name: Compare executable, local vs remote
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
remote_path: /large_bin_file.bin
local_path: "{{ test_directory }}/large_bin_file_from_pod.bin"
always:
- name: Delete temporary directory created for the test
ansible.builtin.file:
path: "{{ test_directory }}"
state: absent
ignore_errors: true
- name: Delete file created on Pod
k8s_exec:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_one_container.name }}'
command: 'rm {{ item }}'
ignore_errors: true
with_items:
- /large_text_file.txt
- /large_bin_file.bin

View File

@@ -0,0 +1,71 @@
---
- set_fact:
random_content: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits,punctuation length=128') }}"
- name: Copy content into first pod's container
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost.txt
content: '{{ random_content }}'
container: '{{ pod_with_two_container.container[0] }}'
state: to_pod
- name: Assert that content has been copied into first container
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost.txt
container: '{{ pod_with_two_container.container[0] }}'
content: '{{ random_content }}'
kubectl_path: "{{ kubectl_path }}"
- name: Assert that content has not been copied into second container
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost.txt
container: '{{ pod_with_two_container.container[1] }}'
content: '{{ random_content }}'
kubectl_path: "{{ kubectl_path }}"
register: diff
ignore_errors: true
- name: check that diff failed
assert:
that:
- diff is failed
- name: Copy content into second's pod container
k8s_cp:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost_01.txt
content: '{{ random_content }}-secondpod'
container: '{{ pod_with_two_container.container[1] }}'
state: to_pod
- name: Assert that content has not been copied into first container
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost_01.txt
container: '{{ pod_with_two_container.container[0] }}'
content: '{{ random_content }}-secondpod'
kubectl_path: "{{ kubectl_path }}"
ignore_errors: true
register: diff_1
- name: check that diff failed
assert:
that:
- diff_1 is failed
- name: Assert that content has been copied into second container
kubectl_file_compare:
namespace: '{{ copy_namespace }}'
pod: '{{ pod_with_two_container.name }}'
remote_path: /file_from_localhost_01.txt
container: '{{ pod_with_two_container.container[1] }}'
content: '{{ random_content }}-secondpod'
kubectl_path: "{{ kubectl_path }}"

View File

@@ -0,0 +1,33 @@
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ pod_with_one_container.name }}'
spec:
containers:
- name: '{{ pod_with_one_container.container }}'
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ pod_with_two_container.name }}'
spec:
containers:
- name: '{{ pod_with_two_container.container[0] }}'
image: busybox:1.32.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
- name: '{{ pod_with_two_container.container[1] }}'
image: busybox:1.33.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done

View File

@@ -0,0 +1,153 @@
---
- set_fact:
diff_namespace: "diff"
diff_configmap: "diff-configmap"
- block:
- name: Ensure namespace
k8s:
kind: Namespace
name: '{{ diff_namespace }}'
# Using option 'apply' set to 'yes'
- name: Create Pod using apply and diff set to yes
k8s:
namespace: '{{ diff_namespace }}'
apply: yes
template: "pod_diff.j2"
diff: yes
vars:
pod_name: "pod-apply"
pod_image: "busybox:1.32.0"
register: result
- name: check that result has diff attribute
assert:
that:
- result is changed
- result.diff is defined
- name: Update pod definition using apply and diff set to no
k8s:
namespace: '{{ diff_namespace }}'
apply: yes
template: "pod_diff.j2"
diff: no
vars:
pod_name: "pod-apply"
pod_image: "busybox:1.33.0"
register: result
- name: check that output has no diff attribute
assert:
that:
- result is changed
- result.diff is not defined
# Using option 'state=patched'
- name: Create Pod using state=present and diff set to yes
k8s:
namespace: '{{ diff_namespace }}'
state: present
template: "pod_diff.j2"
vars:
pod_name: "pod-patch"
pod_image: "busybox:1.32.0"
register: result
- name: Update pod definition using state=patched
k8s:
namespace: '{{ diff_namespace }}'
state: patched
template: "pod_diff.j2"
diff: no
vars:
pod_name: "pod-patch"
pod_image: "busybox:1.33.0"
pod_label: "patching"
register: result
- name: check that output has no diff attribute
assert:
that:
- result is changed
- result.diff is not defined
- name: Update pod definition using state=patched and diff=yes
k8s:
namespace: '{{ diff_namespace }}'
state: patched
template: "pod_diff.j2"
diff: yes
vars:
pod_name: "pod-patch"
pod_image: "busybox:1.33.0"
pod_label: "running"
register: result
- name: check that output has no diff attribute
assert:
that:
- result is changed
- result.diff is defined
# check diff mode using force=yes
- name: Create a ConfigMap
k8s:
kind: ConfigMap
name: '{{ diff_configmap }}'
namespace: '{{ diff_namespace }}'
definition:
data:
key: "initial value"
diff: yes
register: result
- name: check that output has no diff attribute
assert:
that:
- result is changed
- result.diff is not defined
- name: Update ConfigMap using force and diff=no
k8s:
kind: ConfigMap
name: '{{ diff_configmap }}'
namespace: '{{ diff_namespace }}'
force: yes
definition:
data:
key: "update value with diff=no"
diff: no
register: result
- name: check that output has no diff attribute
assert:
that:
- result is changed
- result.diff is not defined
- name: Update ConfigMap using force and diff=yes
k8s:
kind: ConfigMap
name: '{{ diff_configmap }}'
namespace: '{{ diff_namespace }}'
force: yes
definition:
data:
key: "update value with diff=yes"
diff: yes
register: result
- name: check that output has diff attribute
assert:
that:
- result is changed
- result.diff is defined
always:
- name: Ensure namespace is deleted
k8s:
state: absent
kind: Namespace
name: '{{ diff_namespace }}'

View File

@@ -0,0 +1,238 @@
---
- block:
- name: Set common facts
set_fact:
drain_namespace: "drain"
drain_daemonset_name: "promotheus-dset"
drain_pod_name: "pod-drain"
- name: Create {{ drain_namespace }} namespace
k8s:
kind: Namespace
name: '{{ drain_namespace }}'
# It seems that the default ServiceAccount can take a bit to be created
# right after a cluster is brought up. This can lead to the ServiceAccount
# admission controller rejecting a Pod creation request because the
# ServiceAccount does not yet exist.
- name: Wait for default serviceaccount to be created
k8s_info:
kind: ServiceAccount
name: default
namespace: "{{ drain_namespace }}"
wait: yes
- name: list cluster nodes
k8s_info:
kind: node
register: nodes
- name: Select uncordoned nodes
set_fact:
uncordoned_nodes: "{{ nodes.resources | selectattr('spec.unschedulable', 'undefined') | map(attribute='metadata.name') | list}}"
- name: Assert that at least one node is schedulable
assert:
that:
- uncordoned_nodes | length > 0
- name: select node to drain
set_fact:
node_to_drain: '{{ uncordoned_nodes[0] }}'
- name: Deploy daemonset on cluster
k8s:
namespace: '{{ drain_namespace }}'
definition:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: '{{ drain_daemonset_name }}'
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- '{{ node_to_drain }}'
selector:
matchLabels:
name: prometheus-exporter
template:
metadata:
labels:
name: prometheus-exporter
spec:
containers:
- name: prometheus
image: prom/node-exporter
ports:
- containerPort: 80
- name: Create Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet.
k8s:
namespace: '{{ drain_namespace }}'
wait: yes
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ drain_pod_name }}'
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- '{{ node_to_drain }}'
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
- name: Cordon node
k8s_drain:
state: cordon
name: '{{ node_to_drain }}'
register: cordon
- name: assert that cordon is changed
assert:
that:
- cordon is changed
- name: Test cordon idempotency
k8s_drain:
state: cordon
name: '{{ node_to_drain }}'
register: cordon
- name: assert that cordon is not changed
assert:
that:
- cordon is not changed
- name: Get pods
k8s_info:
kind: Pod
namespace: '{{ drain_namespace }}'
register: Pod
- name: assert that pods are running on cordoned node
assert:
that:
- "{{ Pod.resources | selectattr('status.phase', 'equalto', 'Running') | selectattr('spec.nodeName', 'equalto', node_to_drain) | list | length > 0 }}"
- name: Uncordon node
k8s_drain:
state: uncordon
name: '{{ node_to_drain }}'
register: uncordon
- name: assert that uncordon is changed
assert:
that:
- uncordon is changed
- name: Test uncordon idempotency
k8s_drain:
state: uncordon
name: '{{ node_to_drain }}'
register: uncordon
- name: assert that uncordon is not changed
assert:
that:
- uncordon is not changed
- name: Drain node
k8s_drain:
state: drain
name: '{{ node_to_drain }}'
ignore_errors: true
register: drain_result
- name: assert that drain failed due to DaemonSet managed Pods
assert:
that:
- drain_result is failed
- '"cannot delete DaemonSet-managed Pods" in drain_result.msg'
- '"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet" in drain_result.msg'
- name: Drain node using ignore_daemonsets and force options
k8s_drain:
state: drain
name: '{{ node_to_drain }}'
delete_options:
force: true
ignore_daemonsets: true
wait_timeout: 0
register: drain_result
- name: assert that node has been drained
assert:
that:
- drain_result is changed
- '"node {{ node_to_drain }} marked unschedulable." in drain_result.result'
- name: assert that unmanaged pod were deleted
k8s_info:
namespace: '{{ drain_namespace }}'
kind: Pod
name: '{{ drain_pod_name }}'
register: _result
failed_when: _result.resources
- name: Test drain idempotency
k8s_drain:
state: drain
name: '{{ node_to_drain }}'
delete_options:
force: true
ignore_daemonsets: true
register: drain_result
- name: Check idempotency
assert:
that:
- drain_result is not changed
- name: Get DaemonSet
k8s_info:
kind: DaemonSet
namespace: '{{ drain_namespace }}'
name: '{{ drain_daemonset_name }}'
register: dset_result
- name: assert that daemonset managed pods were not removed
assert:
that:
- dset_result.resources | list | length > 0
- name: Uncordon node
k8s_drain:
state: uncordon
name: '{{ node_to_drain }}'
always:
- name: Uncordon node
k8s_drain:
state: uncordon
name: '{{ node_to_drain }}'
when: node_to_drain is defined
ignore_errors: true
- name: delete namespace
k8s:
state: absent
kind: namespace
name: '{{ drain_namespace }}'

View File

@@ -54,6 +54,7 @@
- name: Check last command status
assert:
that:
- command_status.rc != 0
- command_status.return_code != 0
always:

View File

@@ -36,12 +36,36 @@
path: ~/.kube/config
state: absent
- name: Try to create namespace without default kube config
kubernetes.core.k8s:
name: testing
kind: Namespace
ignore_errors: true
register: result
- name: No default kube config should fail
assert:
that: result is not successful
- name: Using custom config location should succeed
kubernetes.core.k8s:
name: testing
kind: Namespace
kubeconfig: ~/.kube/customconfig
- name: Using an env var to set config location should succeed
kubernetes.core.k8s:
name: testing
kind: Namespace
environment:
K8S_AUTH_KUBECONFIG: ~/.kube/customconfig
- name: Using in-memory kubeconfig should succeed
kubernetes.core.k8s:
name: testing
kind: Namespace
kubeconfig: "{{ lookup('file', '~/.kube/customconfig') | from_yaml }}"
always:
- name: Return kubeconfig
copy:
@@ -365,6 +389,38 @@
register: k8s_info_testing6
failed_when: not k8s_info_testing6.resources or k8s_info_testing6.resources[0].status.phase != "Active"
- name: Create large configmap data
command: dd if=/dev/urandom bs=500K count=1
register: cmap_data
- name: Create configmap with large value
k8s:
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: testmap
namespace: testing
data:
testkey: "{{ cmap_data.stdout | b64encode }}"
wait: true
register: result
- assert:
that:
- result is changed
- name: Retrieve configmap
k8s_info:
kind: ConfigMap
namespace: testing
name: testmap
register: result
- assert:
that:
- result.resources[0].data.testkey == "{{ cmap_data.stdout | b64encode }}"
always:
- name: Delete all namespaces
k8s:

View File

@@ -43,6 +43,7 @@
value: busybox:glibc
check_mode: yes
register: result
diff: yes
- name: Assert patch was made
assert:
@@ -50,6 +51,7 @@
- result.changed
- result.result.metadata.labels.label2 == "bar"
- result.result.spec.containers[0].image == "busybox:glibc"
- result.diff
- name: Describe pod
kubernetes.core.k8s_info:
@@ -82,6 +84,7 @@
assert:
that:
- result.changed
- result.diff is not defined
- name: Describe pod
kubernetes.core.k8s_info:

View File

@@ -0,0 +1,657 @@
---
- block:
- set_fact:
selector_namespace: "selector"
selector_pod_delete: "pod-selector-delete"
selector_pod_apply: "pod-selector-apply"
selector_pod_create:
- "pod-selector-apply-00"
- "pod-selector-apply-01"
- "pod-selector-apply-02"
- "pod-selector-apply-03"
- name: Ensure namespace selector
k8s:
kind: namespace
name: '{{ selector_namespace }}'
# Resource deletion using label selector (equality-based requirement)
- name: Create simple pod
k8s:
namespace: '{{ selector_namespace }}'
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_delete }}-00'
labels:
ansible.dev/team: "cloud"
ansible.release/version: upstream
ansible.dev/test: "true"
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
- name: Delete all resource using selector
k8s:
state: absent
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- ansible.dev/team=cloud
wait: yes
wait_timeout: 180
- name: Ensure resources have been deleted
k8s_info:
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- ansible.dev/team=cloud
register: result
- assert:
that:
- result.resources == []
# Resource deletion using label selector (set-based requirement)
- name: Create simple pod
k8s:
namespace: '{{ selector_namespace }}'
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_delete }}-01'
labels:
environment: production
spec:
containers:
- name: c0
image: alpine:3.14.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
- name: Delete all resource using selector
k8s:
state: absent
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- environment in (test, qa)
wait: yes
wait_timeout: 180
register: result
- name: check that no resources were deleted
assert:
that:
- result is not changed
- name: Ensure resources have not been deleted
k8s_info:
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- environment in (production)
register: result
- assert:
that:
- result.resources | list | length > 0
- name: Delete all resource using selector
k8s:
state: absent
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- environment in (production)
wait: yes
wait_timeout: 180
register: result
- name: check result is changed
assert:
that:
- result is changed
- name: Ensure resources have not been deleted
k8s_info:
kind: Pod
namespace: '{{ selector_namespace }}'
label_selectors:
- environment in (production)
register: result
- assert:
that:
- result.resources | list | length == 0
# Resource creation using label selector
- name: Create simple pod using label_selectors option (equality-based requirement)
k8s:
namespace: '{{ selector_namespace }}'
label_selectors:
- container.image=fedora
definition: |
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[0] }}'
labels:
container.image: busybox
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[1] }}'
labels:
container.image: alpine
spec:
containers:
- name: c0
image: alpine
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[2] }}'
labels:
container.image: python
release: dev
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[3] }}'
labels:
container.image: python
release: dev
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
register: result
- assert:
that:
- result is not changed
- name: Create simple pod using label_selectors option
k8s:
namespace: '{{ selector_namespace }}'
label_selectors:
- container.image==alpine
definition: |
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[0] }}'
labels:
container.image: busybox
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[1] }}'
labels:
container.image: alpine
spec:
containers:
- name: c0
image: alpine
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[2] }}'
labels:
container.image: python
environment: test
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[3] }}'
labels:
container.image: python
environment: production
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
register: result
- assert:
that:
- result is changed
- name: list pod created
k8s_info:
namespace: '{{ selector_namespace }}'
kind: Pod
label_selectors:
- container.image
register: pod_created
- name: Validate that pod with matching label was created
assert:
that:
- pods_created | length == 1
- selector_pod_create[1] in pods_created
vars:
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
- name: Create simple pod using label_selectors option (set-based requirement)
k8s:
namespace: '{{ selector_namespace }}'
label_selectors:
- "!environment"
definition: |
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[0] }}'
labels:
container.image: busybox
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[1] }}'
labels:
container.image: alpine
spec:
containers:
- name: c0
image: alpine
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[2] }}'
labels:
container.image: python
environment: test
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[3] }}'
labels:
container.image: python
environment: production
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
register: result
- assert:
that:
- result is changed
- name: list pod created
k8s_info:
namespace: '{{ selector_namespace }}'
kind: Pod
label_selectors:
- container.image
register: pod_created
- name: Validate that pod with matching label was created
assert:
that:
- pods_created | length == 2
- selector_pod_create[0] in pods_created
vars:
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
- name: Create simple pod using label_selectors option (set-based requirement)
k8s:
namespace: '{{ selector_namespace }}'
label_selectors:
- environment in (test)
definition: |
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[0] }}'
labels:
container.image: busybox
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[1] }}'
labels:
container.image: alpine
spec:
containers:
- name: c0
image: alpine
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[2] }}'
labels:
container.image: python
environment: test
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[3] }}'
labels:
container.image: python
environment: production
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
register: result
- assert:
that:
- result is changed
- name: list pod created
k8s_info:
namespace: '{{ selector_namespace }}'
kind: Pod
label_selectors:
- container.image
register: pod_created
- name: Validate that pod with matching label was created
assert:
that:
- pods_created | length == 3
- selector_pod_create[2] in pods_created
vars:
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
- name: Create simple pod using label_selectors option (set-based requirement)
k8s:
namespace: '{{ selector_namespace }}'
label_selectors:
- environment notin (test)
definition: |
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[0] }}'
labels:
container.image: busybox
spec:
containers:
- name: c0
image: busybox
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[1] }}'
labels:
container.image: alpine
spec:
containers:
- name: c0
image: alpine
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[2] }}'
labels:
container.image: python
environment: test
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
---
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_create[3] }}'
labels:
container.image: python
environment: production
spec:
containers:
- name: c0
image: python
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
register: result
- assert:
that:
- result is changed
- name: list pod created
k8s_info:
namespace: '{{ selector_namespace }}'
kind: Pod
label_selectors:
- container.image
register: pod_created
- name: Validate that pod with matching label was created
assert:
that:
- pods_created | length == 4
- selector_pod_create[3] in pods_created
vars:
pods_created: '{{ pod_created.resources | map(attribute="metadata.name") | list }}'
# Resource update using apply
- name: Create simple pod using apply
k8s:
namespace: '{{ selector_namespace }}'
apply: yes
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_apply }}'
labels:
environment: test
spec:
containers:
- name: c0
image: busybox:1.31.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
- name: Apply new pod definition using label_selectors (no match)
k8s:
namespace: '{{ selector_namespace }}'
apply: yes
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_apply }}'
labels:
environment: test
spec:
containers:
- name: c0
image: busybox:1.33.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
label_selectors:
- environment=qa
register: result
- name: check task output
assert:
that:
- result is not changed
- '"filtered by label_selectors" in result.msg'
- name: Apply new pod definition using label_selectors
k8s:
namespace: '{{ selector_namespace }}'
apply: yes
definition:
apiVersion: v1
kind: Pod
metadata:
name: '{{ selector_pod_apply }}'
labels:
environment: test
spec:
containers:
- name: c0
image: busybox:1.33.0
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done
label_selectors:
- environment!=qa
register: result
- name: check task output
assert:
that:
- result is changed
always:
- name: Ensure namespace is deleted
k8s:
kind: Namespace
name: '{{ selector_namespace }}'
state: absent
ignore_errors: true

View File

@@ -1,6 +1,12 @@
---
- block:
# https://github.com/ansible-collections/kubernetes.core/issues/9
- set_fact:
pre_test1: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
pre_test2: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
pre_test3: "{{ query('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
pre_test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
# https://github.com/ansible-collections/kubernetes.core/issues/147
- name: Create a namespace with label
kubernetes.core.k8s:
definition:
@@ -12,15 +18,45 @@
namespace_label: "app_development"
- set_fact:
namespace_info: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test1: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development', wantlist=True) }}"
test2: "{{ query('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test3: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one', wantlist=True) }}"
test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
test5: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test6: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
test7: "{{ lookup('kubernetes.core.k8s', kind='Ingress', api_version='networking.k8s.io/vINVALID', errors='ignore') }}"
- name: Check if the returned value is list with a single element
- set_fact:
test8: "{{ lookup('kubernetes.core.k8s', kind='Ingress', api_version='networking.k8s.io/vINVALID') }}"
ignore_errors: true
- name: Assert that every test is passed
assert:
that:
- namespace_info is iterable
- not namespace_info is string
- not namespace_info is mapping
- namespace_info | length == 1
# Before creating object
- pre_test1 is sequence and pre_test1 is not string
- pre_test1 | length == 0
- pre_test2 is sequence and pre_test2 is not string
- pre_test2 | length == 0
- pre_test3 is sequence and pre_test3 is not string
- pre_test3 | length == 0
- pre_test4 is sequence and pre_test4 is not string
- pre_test4 | length == 0
# After creating object
- test1 is sequence and test1 is not string
- test1 | length == 1
- test2 is sequence and test2 is not string
- test2 | length == 1
- test3 is sequence and test3 is not string
- test3 | length == 1
- test4 is sequence and test4 is not string
- test4 | length == 1
# Without wantlist=True lookup should return mapping
- test5 is mapping
- test6 is mapping
# errors='ignore'
- test7 is string
- test8 is not defined
- name: Create another namespace with label
kubernetes.core.k8s:
@@ -33,22 +69,44 @@
namespace_label: "app_development"
- set_fact:
namespace_info: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test1: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development', wantlist=True) }}"
test2: "{{ query('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test3: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one', wantlist=True) }}"
test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
test5: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-two', wantlist=True) }}"
test6: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-two') }}"
test7: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
test8: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-one') }}"
test9: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name='app-development-two') }}"
- name: Check if the returned value is list with 2 elements
- name: Assert that every test is passed after creating second object
assert:
that:
- namespace_info is iterable
- not namespace_info is string
- not namespace_info is mapping
- namespace_info | length == 2
# After creating second object
- test1 is sequence and test1 is not string
- test1 | length == 2
- test2 is sequence and test2 is not string
- test2 | length == 2
- test3 is sequence and test3 is not string
- test3 | length == 1
- test4 is sequence and test4 is not string
- test4 | length == 1
- test5 is sequence and test5 is not string
- test5 | length == 1
- test6 is sequence and test6 is not string
- test6 | length == 1
# When label_selector is used it returns list irrespective of wantlist=True
- test7 is sequence and test7 is not string
# Without wantlist=True lookup should return mapping
- test8 is mapping
- test9 is mapping
always:
- name: Ensure that namespace is removed
k8s:
kind: Namespace
name: "{{ item }}"
name: "app-development-{{ item }}"
state: absent
with_items:
- app-development-one
- app-development-two
- one
- two

View File

@@ -0,0 +1,103 @@
---
- block:
- set_fact:
kustomize_ns: "kustomize"
- name: create environment for test
block:
- name: Ensure namespace
k8s:
kind: Namespace
name: "{{ kustomize_ns }}"
- name: Create temp directory
tempfile:
state: directory
suffix: .test
register: _tmp_dir
- set_fact:
tmp_dir_path: "{{ _tmp_dir.path }}"
- set_fact:
kustomize_dir: "{{ tmp_dir_path }}/kustomization"
- name: create kustomize directory
file:
path: "{{ kustomize_dir }}"
state: directory
- name: create kustomization file
copy:
content: '{{ item.content }}'
dest: '{{ item.dest }}'
with_items:
- content: |
configMapGenerator:
- name: test-confmap-
files:
- data.properties
dest: "{{ kustomize_dir }}/kustomization.yaml"
- content: "project=ansible"
dest: "{{ kustomize_dir }}/data.properties"
- name: copy script to install kustomize
get_url:
url: https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh
dest: "{{ tmp_dir_path }}"
- name: make script as executable
file:
path: "{{ tmp_dir_path }}/install_kustomize.sh"
mode: 0755
- name: Install kustomize
command: "{{ tmp_dir_path }}/install_kustomize.sh"
args:
chdir: "{{ tmp_dir_path }}"
register: _install
- set_fact:
kustomize_binary: "{{ _install.stdout | regex_search('kustomize installed to (.*)', '\\1') | list | join('') }}"
kubectl_binary: "{{ tmp_dir_path }}/kubectl"
- name: Download kubeclt executable used to compare results
get_url:
url: https://dl.k8s.io/release/v1.21.3/bin/linux/amd64/kubectl
dest: "{{ kubectl_binary }}"
- name: make kubectl executable
ansible.builtin.file:
path: "{{ kubectl_binary }}"
mode: "+x"
- name: Run lookup using kustomize binary
set_fact:
resource_kustomize: "{{ lookup('kubernetes.core.kustomize', binary_path=kustomize_binary, dir=kustomize_dir) }}"
- name: Run lookup using kubectl binary
set_fact:
resource_kubectl: "{{ lookup('kubernetes.core.kustomize', binary_path=kubectl_binary, dir=kustomize_dir) }}"
- name: assert output are the same
assert:
that:
- resource_kubectl == resource_kustomize
- name: create kubernetes resource using lookup plugin
k8s:
namespace: "{{ kustomize_ns }}"
definition: "{{ lookup('kubernetes.core.kustomize', dir=kustomize_dir, opt_dirs=tmp_dir_path) }}"
always:
- name: Delete namespace
k8s:
kind: Namespace
name: "{{ kustomize_ns }}"
state: absent
ignore_errors: true
- name: Delete temporary directory
file:
state: absent
path: "{{ tmp_dir_path }}"

View File

@@ -54,6 +54,7 @@
replicas: 0
wait: yes
register: scale_down
diff: true
- name: Get pods in scale-deploy
k8s_info:
@@ -143,7 +144,7 @@
that:
- scale_up is changed
- '"duration" in scale_up'
- scale_up.diff
- scale_up.diff is not defined
- scale_up_further_deploy_pods.resources | length == 2
- name: Don't scale the deployment up
@@ -170,7 +171,7 @@
assert:
that:
- scale_up_noop is not changed
- not scale_up_noop.diff
- scale_up_noop.diff is not defined
- scale_up_noop_pods.resources | length == 2
- '"duration" in scale_up_noop'
@@ -183,6 +184,7 @@
replicas: 1
wait: no
register: scale_down_no_wait
diff: true
- name: Ensure that scale down succeeds
k8s_info:

View File

@@ -22,7 +22,7 @@
assert:
that:
- r.failed
- "'is only supported parameter for' in r.msg"
- "'is only a supported parameter for' in r.msg"
- name: Specify both definition and template
kubernetes.core.k8s:

View File

@@ -0,0 +1,240 @@
- block:
- name: Create namespace
k8s:
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ validate_namespace }}"
- name: Create temp directory
tempfile:
state: directory
suffix: .test
register: remote_tmp_dir
- set_fact:
remote_tmp_dir: "{{ remote_tmp_dir.path }}"
- set_fact:
virtualenv: "{{ remote_tmp_dir }}/virtualenv"
virtualenv_command: "virtualenv --python {{ ansible_python_interpreter }}"
- set_fact:
virtualenv_interpreter: "{{ virtualenv }}/bin/python"
- pip:
name:
- kubernetes
virtualenv: "{{ virtualenv }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: false
- name: Validate should fail gracefully without kubernetes-validate
k8s:
definition: &cmap
apiVersion: v1
kind: ConfigMap
metadata:
name: testmap
namespace: "{{ validate_namespace }}"
data:
mykey: myval
validate:
fail_on_error: true
ignore_errors: true
register: k8s_no_validate
vars:
ansible_python_interpreter: "{{ virtualenv_interpreter }}"
- assert:
that:
- k8s_no_validate is failed
- "k8s_no_validate.msg == 'kubernetes-validate python library is required to validate resources'"
- file:
path: "{{ virtualenv }}"
state: absent
- pip:
name:
- kubernetes
- kubernetes-validate
virtualenv: "{{ virtualenv }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: false
- block:
- name: Simple ConfigMap should validate
k8s:
definition: *cmap
validate:
fail_on_error: true
- name: ConfigMap with extra properties should validate without strict
k8s:
definition:
<<: *cmap
extra: stuff
validate:
fail_on_error: true
strict: false
- name: ConfigMap with extra properties should not validate with strict
k8s:
definition:
<<: *cmap
extra: stuff
validate:
fail_on_error: true
strict: true
ignore_errors: true
register: result
- assert:
that:
- result is failed
- "\"('extra' was unexpected)\" is in result.msg"
- name: Property with invalid type should fail with strict
k8s:
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: testdeploy
namespace: "{{ validate_namespace }}"
labels:
app: foo
spec:
replicas: lots
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: busybox
image: busybox
validate:
fail_on_error: true
strict: false
ignore_errors: true
register: result
- assert:
that:
- result is failed
- result.status is not defined
- "\"'lots' is not of type 'integer'\" in result.msg"
- name: Create CRD
k8s:
definition: &crd
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: foobars.example.com
spec:
group: example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
foo:
type: string
scope: Namespaced
names:
plural: foobars
singular: foobar
kind: Foobar
- pause:
seconds: 5
- name: Adding CRD should succeed with warning
k8s:
definition:
apiVersion: example.com/v1
kind: Foobar
metadata:
name: foobar
namespace: "{{ validate_namespace }}"
foo: bar
validate:
fail_on_error: true
strict: true
register: result
- assert:
that:
- result is successful
- "'warnings' in result"
vars:
ansible_python_interpreter: "{{ virtualenv_interpreter }}"
- name: stat default kube config
stat:
path: "~/.kube/config"
register: _stat
- name: validate that in-memory kubeconfig authentication failed for kubernetes < 17.17.0
block:
- set_fact:
virtualenv_kubeconfig: "{{ remote_tmp_dir }}/kubeconfig"
- pip:
name:
- "kubernetes<17.17.0"
virtualenv: "{{ virtualenv_kubeconfig }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: false
- name: list namespace using in-memory kubeconfig
k8s_info:
kind: Namespace
kubeconfig: "{{ lookup('file', '~/.kube/config') | from_yaml }}"
register: _result
ignore_errors: true
vars:
ansible_python_interpreter: "{{ virtualenv_kubeconfig }}/bin/python"
- name: assert that task failed with proper message
assert:
that:
- '"kubernetes >= 17.17.0 is required" in _result.msg'
when:
- _stat.stat.exists
- _stat.stat.readable
- _stat.stat.isreg
always:
- name: Remove temp directory
file:
path: "{{ remote_tmp_dir }}"
state: absent
ignore_errors: true
- name: Remove namespace
k8s:
kind: Namespace
name: "{{ validate_namespace }}"
state: absent
ignore_errors: true
- name: Remove CRD
k8s:
definition: *crd
state: absent
ignore_errors: true
vars:
validate_namespace: validate
environment:
ENABLE_TURBO_MODE: false

View File

@@ -140,6 +140,111 @@
- ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled
- updated_ds_pods.resources[0].spec.containers[0].image.endswith(":3")
- name: Add a statefulset
k8s:
definition:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: wait-statefulset
namespace: "{{ wait_namespace }}"
spec:
selector:
matchLabels:
app: "{{ k8s_pod_name }}"
template: "{{ k8s_pod_template }}"
wait: yes
wait_sleep: 5
wait_timeout: 180
vars:
k8s_pod_name: wait-sts
k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1
k8s_pod_command:
- sleep
- "600"
register: sts
- name: Check that statefulset wait worked
assert:
that:
- sts.result.spec.replicas == sts.result.status.readyReplicas
- name: Update a statefulset in check_mode
k8s:
definition:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: wait-statefulset
namespace: "{{ wait_namespace }}"
spec:
selector:
matchLabels:
app: "{{ k8s_pod_name }}"
updateStrategy:
type: RollingUpdate
template: "{{ k8s_pod_template }}"
wait: yes
wait_sleep: 3
wait_timeout: 180
vars:
k8s_pod_name: wait-sts
k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2
k8s_pod_command:
- sleep
- "600"
register: update_sts_check_mode
check_mode: yes
- name: Check that check_mode result contains the changes
assert:
that:
- update_sts_check_mode is changed
- "update_sts_check_mode.result.spec.template.spec.containers[0].image == 'gcr.io/kuar-demo/kuard-amd64:2'"
- name: Update a statefulset
k8s:
definition:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: wait-statefulset
namespace: "{{ wait_namespace }}"
spec:
selector:
matchLabels:
app: "{{ k8s_pod_name }}"
updateStrategy:
type: RollingUpdate
template: "{{ k8s_pod_template }}"
wait: yes
wait_sleep: 3
wait_timeout: 180
vars:
k8s_pod_name: wait-sts
k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:3
k8s_pod_command:
- sleep
- "600"
register: sts
- name: Get updated pods
k8s_info:
api_version: v1
kind: Pod
namespace: "{{ wait_namespace }}"
label_selectors:
- app=wait-sts
field_selectors:
- status.phase=Running
register: updated_sts_pods
- name: Check that statefulset wait worked
assert:
that:
- sts.result.spec.replicas == sts.result.status.readyReplicas
- updated_sts_pods.resources[0].spec.containers[0].image.endswith(":3")
- name: Add a crashing pod
k8s:
definition:

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: {{ pod_name }}
labels:
ansible: {{ pod_label | default('demo') }}
spec:
containers:
- name: c0
image: {{ pod_image }}
command:
- /bin/sh
- -c
- while true;do date;sleep 5; done

1
plugins/action/k8s_cp.py Symbolic link
View File

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

1
plugins/action/k8s_drain.py Symbolic link
View File

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

View File

@@ -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)

View File

@@ -245,6 +245,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):

View File

@@ -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.

View File

@@ -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
'''

View File

@@ -21,7 +21,10 @@ DOCUMENTATION = '''
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:
@@ -119,23 +122,23 @@ DOCUMENTATION = '''
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,11 +148,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') }}"
"""
RETURN = """
@@ -264,7 +267,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):

133
plugins/lookup/kustomize.py Normal file
View File

@@ -0,0 +1,133 @@
#
# Copyright 2021 Red Hat | Ansible
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
lookup: 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:
- One ore more object definitions returned from the tool execution.
type: complex
contains:
api_version:
description: The versioned schema of this representation of an object.
returned: success
type: str
kind:
description: Represents the REST resource this object represents.
returned: success
type: str
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
"""
from ansible.errors import AnsibleLookupError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.process import get_bin_path
import subprocess
def get_binary_from_path(name, opt_dirs=None):
opt_arg = {}
try:
if opt_dirs is not None:
if not isinstance(opt_dirs, list):
opt_dirs = [opt_dirs]
opt_arg['opt_dirs'] = opt_dirs
bin_path = get_bin_path(name, **opt_arg)
return bin_path
except ValueError:
return None
def run_command(command):
cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return cmd.communicate()
class LookupModule(LookupBase):
def run(self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs):
executable_path = binary_path
if executable_path is None:
executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
if executable_path is None:
executable_path = get_binary_from_path(name="kubectl", opt_dirs=opt_dirs)
# validate that at least one tool was found
if executable_path is None:
raise AnsibleLookupError("Failed to find required executable 'kubectl' and 'kustomize' in paths")
# check input directory
kustomization_dir = dir
command = [executable_path]
if executable_path.endswith('kustomize'):
command += ['build', kustomization_dir]
elif executable_path.endswith('kubectl'):
command += ['kustomize', kustomization_dir]
else:
raise AnsibleLookupError("unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(executable_path))
(out, err) = run_command(command)
if err:
raise AnsibleLookupError("kustomize command failed with: {0}".format(err.decode("utf-8")))
return [out.decode('utf-8')]

View File

@@ -3,4 +3,22 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule # noqa: F401
import os
from ansible.module_utils.common.validation import check_type_bool
try:
enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
except TypeError:
enable_turbo_mode = False
if enable_turbo_mode:
try:
from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
AnsibleTurboModule as AnsibleModule,
) # noqa: F401
AnsibleModule.collection_name = "kubernetes.core"
except ImportError:
from ansible.module_utils.basic import AnsibleModule # noqa: F401
else:
from ansible.module_utils.basic import AnsibleModule # noqa: F401

View File

@@ -19,7 +19,7 @@ AUTH_PROXY_HEADERS_SPEC = dict(
AUTH_ARG_SPEC = {
'kubeconfig': {
'type': 'path',
'type': 'raw',
},
'context': {},
'host': {},

View File

@@ -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):

View File

@@ -29,6 +29,7 @@ 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.selector import LabelSelectorFilter
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.six import iteritems, string_types
@@ -53,7 +54,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,9 +120,8 @@ 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) is not None:
@@ -147,7 +147,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 +174,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 +184,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 +211,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 +224,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):
@@ -349,7 +365,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 +374,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 +390,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 +407,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 +416,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 +447,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 +603,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 +620,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 +656,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 +688,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 +700,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 +737,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 +830,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 +865,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"

View 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)

View File

@@ -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:

View File

@@ -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
View 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()

View File

@@ -0,0 +1,411 @@
#!/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
from datetime import datetime
import time
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 V1beta1Eviction, V1DeleteOptions
from kubernetes.client.exceptions import ApiException
except ImportError:
# ImportError are managed by the common module already.
pass
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:
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 = V1beta1Eviction(**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())
k8s_drain = K8sDrainAnsible(module)
k8s_drain.execute_module()
if __name__ == '__main__':
main()

View File

@@ -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
)

View File

@@ -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"

View File

@@ -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,

View File

@@ -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)

View File

@@ -1 +1,4 @@
kubernetes-validate
coverage==4.5.4
pytest
pytest-xdist

View File

@@ -1,18 +1,249 @@
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart/templates/configmap.yaml yamllint!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/__init__.py compile-2.6!skip
plugins/module_utils/__init__.py compile-2.7!skip
plugins/module_utils/__init__.py compile-3.5!skip
plugins/module_utils/__init__.py future-import-boilerplate!skip
plugins/module_utils/__init__.py import-2.6!skip
plugins/module_utils/__init__.py import-2.7!skip
plugins/module_utils/__init__.py import-3.5!skip
plugins/module_utils/__init__.py metaclass-boilerplate!skip
plugins/module_utils/ansiblemodule.py compile-2.6!skip
plugins/module_utils/ansiblemodule.py compile-2.7!skip
plugins/module_utils/ansiblemodule.py compile-3.5!skip
plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
plugins/module_utils/ansiblemodule.py import-2.6!skip
plugins/module_utils/ansiblemodule.py import-2.7!skip
plugins/module_utils/ansiblemodule.py import-3.5!skip
plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
plugins/module_utils/apply.py compile-2.6!skip
plugins/module_utils/apply.py compile-2.7!skip
plugins/module_utils/apply.py compile-3.5!skip
plugins/module_utils/apply.py future-import-boilerplate!skip
plugins/module_utils/apply.py import-2.6!skip
plugins/module_utils/apply.py import-2.7!skip
plugins/module_utils/apply.py import-3.5!skip
plugins/module_utils/apply.py metaclass-boilerplate!skip
plugins/module_utils/args_common.py compile-2.6!skip
plugins/module_utils/args_common.py compile-2.7!skip
plugins/module_utils/args_common.py compile-3.5!skip
plugins/module_utils/args_common.py future-import-boilerplate!skip
plugins/module_utils/args_common.py import-2.6!skip
plugins/module_utils/args_common.py import-2.7!skip
plugins/module_utils/args_common.py import-3.5!skip
plugins/module_utils/args_common.py metaclass-boilerplate!skip
plugins/module_utils/client/discovery.py future-import-boilerplate!skip
plugins/module_utils/client/discovery.py import-2.6!skip
plugins/module_utils/client/discovery.py import-2.7!skip
plugins/module_utils/client/discovery.py import-3.5!skip
plugins/module_utils/client/discovery.py import-3.6!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.8!skip
plugins/module_utils/client/discovery.py import-3.9!skip
plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
plugins/module_utils/client/resource.py import-2.6!skip
plugins/module_utils/client/resource.py import-2.7!skip
plugins/module_utils/client/resource.py import-3.5!skip
plugins/module_utils/client/resource.py import-3.6!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.8!skip
plugins/module_utils/client/resource.py import-3.9!skip
plugins/module_utils/common.py compile-2.6!skip
plugins/module_utils/common.py compile-2.7!skip
plugins/module_utils/common.py compile-3.5!skip
plugins/module_utils/common.py future-import-boilerplate!skip
plugins/module_utils/common.py import-2.6!skip
plugins/module_utils/common.py import-2.7!skip
plugins/module_utils/common.py import-3.5!skip
plugins/module_utils/common.py metaclass-boilerplate!skip
plugins/module_utils/exceptions.py compile-2.6!skip
plugins/module_utils/exceptions.py compile-2.7!skip
plugins/module_utils/exceptions.py compile-3.5!skip
plugins/module_utils/exceptions.py future-import-boilerplate!skip
plugins/module_utils/exceptions.py import-2.6!skip
plugins/module_utils/exceptions.py import-2.7!skip
plugins/module_utils/exceptions.py import-3.5!skip
plugins/module_utils/exceptions.py metaclass-boilerplate!skip
plugins/module_utils/hashes.py compile-2.6!skip
plugins/module_utils/hashes.py compile-2.7!skip
plugins/module_utils/hashes.py compile-3.5!skip
plugins/module_utils/hashes.py future-import-boilerplate!skip
plugins/module_utils/hashes.py import-2.6!skip
plugins/module_utils/hashes.py import-2.7!skip
plugins/module_utils/hashes.py import-3.5!skip
plugins/module_utils/hashes.py metaclass-boilerplate!skip
plugins/module_utils/helm.py compile-2.6!skip
plugins/module_utils/helm.py compile-2.7!skip
plugins/module_utils/helm.py compile-3.5!skip
plugins/module_utils/helm.py future-import-boilerplate!skip
plugins/module_utils/helm.py import-2.6!skip
plugins/module_utils/helm.py import-2.7!skip
plugins/module_utils/helm.py import-3.5!skip
plugins/module_utils/helm.py metaclass-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py compile-2.6!skip
plugins/module_utils/k8sdynamicclient.py compile-2.7!skip
plugins/module_utils/k8sdynamicclient.py compile-3.5!skip
plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py import-2.6!skip
plugins/module_utils/k8sdynamicclient.py import-2.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.5!skip
plugins/module_utils/k8sdynamicclient.py import-3.6!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.8!skip
plugins/module_utils/k8sdynamicclient.py import-3.9!skip
plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
plugins/modules/__init__.py compile-2.6!skip
plugins/modules/__init__.py compile-2.7!skip
plugins/modules/__init__.py compile-3.5!skip
plugins/modules/__init__.py future-import-boilerplate!skip
plugins/modules/__init__.py import-2.6!skip
plugins/modules/__init__.py import-2.7!skip
plugins/modules/__init__.py import-3.5!skip
plugins/modules/__init__.py metaclass-boilerplate!skip
plugins/modules/helm.py compile-2.6!skip
plugins/modules/helm.py compile-2.7!skip
plugins/modules/helm.py compile-3.5!skip
plugins/modules/helm.py future-import-boilerplate!skip
plugins/modules/helm.py import-2.6!skip
plugins/modules/helm.py import-2.7!skip
plugins/modules/helm.py import-3.5!skip
plugins/modules/helm.py metaclass-boilerplate!skip
plugins/modules/helm_info.py compile-2.6!skip
plugins/modules/helm_info.py compile-2.7!skip
plugins/modules/helm_info.py compile-3.5!skip
plugins/modules/helm_info.py future-import-boilerplate!skip
plugins/modules/helm_info.py import-2.6!skip
plugins/modules/helm_info.py import-2.7!skip
plugins/modules/helm_info.py import-3.5!skip
plugins/modules/helm_info.py metaclass-boilerplate!skip
plugins/modules/helm_plugin.py compile-2.6!skip
plugins/modules/helm_plugin.py compile-2.7!skip
plugins/modules/helm_plugin.py compile-3.5!skip
plugins/modules/helm_plugin.py future-import-boilerplate!skip
plugins/modules/helm_plugin.py import-2.6!skip
plugins/modules/helm_plugin.py import-2.7!skip
plugins/modules/helm_plugin.py import-3.5!skip
plugins/modules/helm_plugin.py metaclass-boilerplate!skip
plugins/modules/helm_plugin_info.py compile-2.6!skip
plugins/modules/helm_plugin_info.py compile-2.7!skip
plugins/modules/helm_plugin_info.py compile-3.5!skip
plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
plugins/modules/helm_plugin_info.py import-2.6!skip
plugins/modules/helm_plugin_info.py import-2.7!skip
plugins/modules/helm_plugin_info.py import-3.5!skip
plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
plugins/modules/helm_repository.py compile-2.6!skip
plugins/modules/helm_repository.py compile-2.7!skip
plugins/modules/helm_repository.py compile-3.5!skip
plugins/modules/helm_repository.py future-import-boilerplate!skip
plugins/modules/helm_repository.py import-2.6!skip
plugins/modules/helm_repository.py import-2.7!skip
plugins/modules/helm_repository.py import-3.5!skip
plugins/modules/helm_repository.py metaclass-boilerplate!skip
plugins/modules/helm_template.py compile-2.6!skip
plugins/modules/helm_template.py compile-2.7!skip
plugins/modules/helm_template.py compile-3.5!skip
plugins/modules/helm_template.py future-import-boilerplate!skip
plugins/modules/helm_template.py import-2.6!skip
plugins/modules/helm_template.py import-2.7!skip
plugins/modules/helm_template.py import-3.5!skip
plugins/modules/helm_template.py metaclass-boilerplate!skip
plugins/modules/k8s.py compile-2.6!skip
plugins/modules/k8s.py compile-2.7!skip
plugins/modules/k8s.py compile-3.5!skip
plugins/modules/k8s.py future-import-boilerplate!skip
plugins/modules/k8s.py import-2.6!skip
plugins/modules/k8s.py import-2.7!skip
plugins/modules/k8s.py import-3.5!skip
plugins/modules/k8s.py metaclass-boilerplate!skip
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_cluster_info.py compile-2.6!skip
plugins/modules/k8s_cluster_info.py compile-2.7!skip
plugins/modules/k8s_cluster_info.py compile-3.5!skip
plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
plugins/modules/k8s_cluster_info.py import-2.6!skip
plugins/modules/k8s_cluster_info.py import-2.7!skip
plugins/modules/k8s_cluster_info.py import-3.5!skip
plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
plugins/modules/k8s_exec.py compile-2.6!skip
plugins/modules/k8s_exec.py compile-2.7!skip
plugins/modules/k8s_exec.py compile-3.5!skip
plugins/modules/k8s_exec.py future-import-boilerplate!skip
plugins/modules/k8s_exec.py import-2.6!skip
plugins/modules/k8s_exec.py import-2.7!skip
plugins/modules/k8s_exec.py import-3.5!skip
plugins/modules/k8s_exec.py metaclass-boilerplate!skip
plugins/modules/k8s_info.py compile-2.6!skip
plugins/modules/k8s_info.py compile-2.7!skip
plugins/modules/k8s_info.py compile-3.5!skip
plugins/modules/k8s_info.py future-import-boilerplate!skip
plugins/modules/k8s_info.py import-2.6!skip
plugins/modules/k8s_info.py import-2.7!skip
plugins/modules/k8s_info.py import-3.5!skip
plugins/modules/k8s_info.py metaclass-boilerplate!skip
plugins/modules/k8s_json_patch.py compile-2.6!skip
plugins/modules/k8s_json_patch.py compile-2.7!skip
plugins/modules/k8s_json_patch.py compile-3.5!skip
plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
plugins/modules/k8s_json_patch.py import-2.6!skip
plugins/modules/k8s_json_patch.py import-2.7!skip
plugins/modules/k8s_json_patch.py import-3.5!skip
plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
plugins/modules/k8s_log.py compile-2.6!skip
plugins/modules/k8s_log.py compile-2.7!skip
plugins/modules/k8s_log.py compile-3.5!skip
plugins/modules/k8s_log.py future-import-boilerplate!skip
plugins/modules/k8s_log.py import-2.6!skip
plugins/modules/k8s_log.py import-2.7!skip
plugins/modules/k8s_log.py import-3.5!skip
plugins/modules/k8s_log.py metaclass-boilerplate!skip
plugins/modules/k8s_rollback.py compile-2.6!skip
plugins/modules/k8s_rollback.py compile-2.7!skip
plugins/modules/k8s_rollback.py compile-3.5!skip
plugins/modules/k8s_rollback.py future-import-boilerplate!skip
plugins/modules/k8s_rollback.py import-2.6!skip
plugins/modules/k8s_rollback.py import-2.7!skip
plugins/modules/k8s_rollback.py import-3.5!skip
plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py compile-2.6!skip
plugins/modules/k8s_scale.py compile-2.7!skip
plugins/modules/k8s_scale.py compile-3.5!skip
plugins/modules/k8s_scale.py future-import-boilerplate!skip
plugins/modules/k8s_scale.py import-2.6!skip
plugins/modules/k8s_scale.py import-2.7!skip
plugins/modules/k8s_scale.py import-3.5!skip
plugins/modules/k8s_scale.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py compile-2.6!skip
plugins/modules/k8s_service.py compile-2.7!skip
plugins/modules/k8s_service.py compile-3.5!skip
plugins/modules/k8s_service.py future-import-boilerplate!skip
plugins/modules/k8s_service.py import-2.6!skip
plugins/modules/k8s_service.py import-2.7!skip
plugins/modules/k8s_service.py import-3.5!skip
plugins/modules/k8s_service.py metaclass-boilerplate!skip
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py validate-modules:return-syntax-error
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py shebang
molecule/default/roles/k8scopy/library/k8s_create_file.py shebang
plugins/modules/k8s_cp.py compile-2.6!skip
plugins/modules/k8s_cp.py compile-2.7!skip
plugins/modules/k8s_cp.py import-2.6!skip
plugins/modules/k8s_cp.py import-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
plugins/module_utils/selector.py future-import-boilerplate!skip
plugins/module_utils/selector.py metaclass-boilerplate!skip
plugins/lookup/kustomize.py future-import-boilerplate!skip
plugins/lookup/kustomize.py metaclass-boilerplate!skip

View File

@@ -1,18 +1,249 @@
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart/templates/configmap.yaml yamllint!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/__init__.py compile-2.6!skip
plugins/module_utils/__init__.py compile-2.7!skip
plugins/module_utils/__init__.py compile-3.5!skip
plugins/module_utils/__init__.py future-import-boilerplate!skip
plugins/module_utils/__init__.py import-2.6!skip
plugins/module_utils/__init__.py import-2.7!skip
plugins/module_utils/__init__.py import-3.5!skip
plugins/module_utils/__init__.py metaclass-boilerplate!skip
plugins/module_utils/ansiblemodule.py compile-2.6!skip
plugins/module_utils/ansiblemodule.py compile-2.7!skip
plugins/module_utils/ansiblemodule.py compile-3.5!skip
plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
plugins/module_utils/ansiblemodule.py import-2.6!skip
plugins/module_utils/ansiblemodule.py import-2.7!skip
plugins/module_utils/ansiblemodule.py import-3.5!skip
plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
plugins/module_utils/apply.py compile-2.6!skip
plugins/module_utils/apply.py compile-2.7!skip
plugins/module_utils/apply.py compile-3.5!skip
plugins/module_utils/apply.py future-import-boilerplate!skip
plugins/module_utils/apply.py import-2.6!skip
plugins/module_utils/apply.py import-2.7!skip
plugins/module_utils/apply.py import-3.5!skip
plugins/module_utils/apply.py metaclass-boilerplate!skip
plugins/module_utils/args_common.py compile-2.6!skip
plugins/module_utils/args_common.py compile-2.7!skip
plugins/module_utils/args_common.py compile-3.5!skip
plugins/module_utils/args_common.py future-import-boilerplate!skip
plugins/module_utils/args_common.py import-2.6!skip
plugins/module_utils/args_common.py import-2.7!skip
plugins/module_utils/args_common.py import-3.5!skip
plugins/module_utils/args_common.py metaclass-boilerplate!skip
plugins/module_utils/client/discovery.py future-import-boilerplate!skip
plugins/module_utils/client/discovery.py import-2.6!skip
plugins/module_utils/client/discovery.py import-2.7!skip
plugins/module_utils/client/discovery.py import-3.5!skip
plugins/module_utils/client/discovery.py import-3.6!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.8!skip
plugins/module_utils/client/discovery.py import-3.9!skip
plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
plugins/module_utils/client/resource.py import-2.6!skip
plugins/module_utils/client/resource.py import-2.7!skip
plugins/module_utils/client/resource.py import-3.5!skip
plugins/module_utils/client/resource.py import-3.6!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.8!skip
plugins/module_utils/client/resource.py import-3.9!skip
plugins/module_utils/common.py compile-2.6!skip
plugins/module_utils/common.py compile-2.7!skip
plugins/module_utils/common.py compile-3.5!skip
plugins/module_utils/common.py future-import-boilerplate!skip
plugins/module_utils/common.py import-2.6!skip
plugins/module_utils/common.py import-2.7!skip
plugins/module_utils/common.py import-3.5!skip
plugins/module_utils/common.py metaclass-boilerplate!skip
plugins/module_utils/exceptions.py compile-2.6!skip
plugins/module_utils/exceptions.py compile-2.7!skip
plugins/module_utils/exceptions.py compile-3.5!skip
plugins/module_utils/exceptions.py future-import-boilerplate!skip
plugins/module_utils/exceptions.py import-2.6!skip
plugins/module_utils/exceptions.py import-2.7!skip
plugins/module_utils/exceptions.py import-3.5!skip
plugins/module_utils/exceptions.py metaclass-boilerplate!skip
plugins/module_utils/hashes.py compile-2.6!skip
plugins/module_utils/hashes.py compile-2.7!skip
plugins/module_utils/hashes.py compile-3.5!skip
plugins/module_utils/hashes.py future-import-boilerplate!skip
plugins/module_utils/hashes.py import-2.6!skip
plugins/module_utils/hashes.py import-2.7!skip
plugins/module_utils/hashes.py import-3.5!skip
plugins/module_utils/hashes.py metaclass-boilerplate!skip
plugins/module_utils/helm.py compile-2.6!skip
plugins/module_utils/helm.py compile-2.7!skip
plugins/module_utils/helm.py compile-3.5!skip
plugins/module_utils/helm.py future-import-boilerplate!skip
plugins/module_utils/helm.py import-2.6!skip
plugins/module_utils/helm.py import-2.7!skip
plugins/module_utils/helm.py import-3.5!skip
plugins/module_utils/helm.py metaclass-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py compile-2.6!skip
plugins/module_utils/k8sdynamicclient.py compile-2.7!skip
plugins/module_utils/k8sdynamicclient.py compile-3.5!skip
plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py import-2.6!skip
plugins/module_utils/k8sdynamicclient.py import-2.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.5!skip
plugins/module_utils/k8sdynamicclient.py import-3.6!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.8!skip
plugins/module_utils/k8sdynamicclient.py import-3.9!skip
plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
plugins/modules/__init__.py compile-2.6!skip
plugins/modules/__init__.py compile-2.7!skip
plugins/modules/__init__.py compile-3.5!skip
plugins/modules/__init__.py future-import-boilerplate!skip
plugins/modules/__init__.py import-2.6!skip
plugins/modules/__init__.py import-2.7!skip
plugins/modules/__init__.py import-3.5!skip
plugins/modules/__init__.py metaclass-boilerplate!skip
plugins/modules/helm.py compile-2.6!skip
plugins/modules/helm.py compile-2.7!skip
plugins/modules/helm.py compile-3.5!skip
plugins/modules/helm.py future-import-boilerplate!skip
plugins/modules/helm.py import-2.6!skip
plugins/modules/helm.py import-2.7!skip
plugins/modules/helm.py import-3.5!skip
plugins/modules/helm.py metaclass-boilerplate!skip
plugins/modules/helm_info.py compile-2.6!skip
plugins/modules/helm_info.py compile-2.7!skip
plugins/modules/helm_info.py compile-3.5!skip
plugins/modules/helm_info.py future-import-boilerplate!skip
plugins/modules/helm_info.py import-2.6!skip
plugins/modules/helm_info.py import-2.7!skip
plugins/modules/helm_info.py import-3.5!skip
plugins/modules/helm_info.py metaclass-boilerplate!skip
plugins/modules/helm_plugin.py compile-2.6!skip
plugins/modules/helm_plugin.py compile-2.7!skip
plugins/modules/helm_plugin.py compile-3.5!skip
plugins/modules/helm_plugin.py future-import-boilerplate!skip
plugins/modules/helm_plugin.py import-2.6!skip
plugins/modules/helm_plugin.py import-2.7!skip
plugins/modules/helm_plugin.py import-3.5!skip
plugins/modules/helm_plugin.py metaclass-boilerplate!skip
plugins/modules/helm_plugin_info.py compile-2.6!skip
plugins/modules/helm_plugin_info.py compile-2.7!skip
plugins/modules/helm_plugin_info.py compile-3.5!skip
plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
plugins/modules/helm_plugin_info.py import-2.6!skip
plugins/modules/helm_plugin_info.py import-2.7!skip
plugins/modules/helm_plugin_info.py import-3.5!skip
plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
plugins/modules/helm_repository.py compile-2.6!skip
plugins/modules/helm_repository.py compile-2.7!skip
plugins/modules/helm_repository.py compile-3.5!skip
plugins/modules/helm_repository.py future-import-boilerplate!skip
plugins/modules/helm_repository.py import-2.6!skip
plugins/modules/helm_repository.py import-2.7!skip
plugins/modules/helm_repository.py import-3.5!skip
plugins/modules/helm_repository.py metaclass-boilerplate!skip
plugins/modules/helm_template.py compile-2.6!skip
plugins/modules/helm_template.py compile-2.7!skip
plugins/modules/helm_template.py compile-3.5!skip
plugins/modules/helm_template.py future-import-boilerplate!skip
plugins/modules/helm_template.py import-2.6!skip
plugins/modules/helm_template.py import-2.7!skip
plugins/modules/helm_template.py import-3.5!skip
plugins/modules/helm_template.py metaclass-boilerplate!skip
plugins/modules/k8s.py compile-2.6!skip
plugins/modules/k8s.py compile-2.7!skip
plugins/modules/k8s.py compile-3.5!skip
plugins/modules/k8s.py future-import-boilerplate!skip
plugins/modules/k8s.py import-2.6!skip
plugins/modules/k8s.py import-2.7!skip
plugins/modules/k8s.py import-3.5!skip
plugins/modules/k8s.py metaclass-boilerplate!skip
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_cluster_info.py compile-2.6!skip
plugins/modules/k8s_cluster_info.py compile-2.7!skip
plugins/modules/k8s_cluster_info.py compile-3.5!skip
plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
plugins/modules/k8s_cluster_info.py import-2.6!skip
plugins/modules/k8s_cluster_info.py import-2.7!skip
plugins/modules/k8s_cluster_info.py import-3.5!skip
plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
plugins/modules/k8s_exec.py compile-2.6!skip
plugins/modules/k8s_exec.py compile-2.7!skip
plugins/modules/k8s_exec.py compile-3.5!skip
plugins/modules/k8s_exec.py future-import-boilerplate!skip
plugins/modules/k8s_exec.py import-2.6!skip
plugins/modules/k8s_exec.py import-2.7!skip
plugins/modules/k8s_exec.py import-3.5!skip
plugins/modules/k8s_exec.py metaclass-boilerplate!skip
plugins/modules/k8s_info.py compile-2.6!skip
plugins/modules/k8s_info.py compile-2.7!skip
plugins/modules/k8s_info.py compile-3.5!skip
plugins/modules/k8s_info.py future-import-boilerplate!skip
plugins/modules/k8s_info.py import-2.6!skip
plugins/modules/k8s_info.py import-2.7!skip
plugins/modules/k8s_info.py import-3.5!skip
plugins/modules/k8s_info.py metaclass-boilerplate!skip
plugins/modules/k8s_json_patch.py compile-2.6!skip
plugins/modules/k8s_json_patch.py compile-2.7!skip
plugins/modules/k8s_json_patch.py compile-3.5!skip
plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
plugins/modules/k8s_json_patch.py import-2.6!skip
plugins/modules/k8s_json_patch.py import-2.7!skip
plugins/modules/k8s_json_patch.py import-3.5!skip
plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
plugins/modules/k8s_log.py compile-2.6!skip
plugins/modules/k8s_log.py compile-2.7!skip
plugins/modules/k8s_log.py compile-3.5!skip
plugins/modules/k8s_log.py future-import-boilerplate!skip
plugins/modules/k8s_log.py import-2.6!skip
plugins/modules/k8s_log.py import-2.7!skip
plugins/modules/k8s_log.py import-3.5!skip
plugins/modules/k8s_log.py metaclass-boilerplate!skip
plugins/modules/k8s_rollback.py compile-2.6!skip
plugins/modules/k8s_rollback.py compile-2.7!skip
plugins/modules/k8s_rollback.py compile-3.5!skip
plugins/modules/k8s_rollback.py future-import-boilerplate!skip
plugins/modules/k8s_rollback.py import-2.6!skip
plugins/modules/k8s_rollback.py import-2.7!skip
plugins/modules/k8s_rollback.py import-3.5!skip
plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py compile-2.6!skip
plugins/modules/k8s_scale.py compile-2.7!skip
plugins/modules/k8s_scale.py compile-3.5!skip
plugins/modules/k8s_scale.py future-import-boilerplate!skip
plugins/modules/k8s_scale.py import-2.6!skip
plugins/modules/k8s_scale.py import-2.7!skip
plugins/modules/k8s_scale.py import-3.5!skip
plugins/modules/k8s_scale.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py compile-2.6!skip
plugins/modules/k8s_service.py compile-2.7!skip
plugins/modules/k8s_service.py compile-3.5!skip
plugins/modules/k8s_service.py future-import-boilerplate!skip
plugins/modules/k8s_service.py import-2.6!skip
plugins/modules/k8s_service.py import-2.7!skip
plugins/modules/k8s_service.py import-3.5!skip
plugins/modules/k8s_service.py metaclass-boilerplate!skip
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py validate-modules:return-syntax-error
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py shebang
molecule/default/roles/k8scopy/library/k8s_create_file.py shebang
plugins/modules/k8s_cp.py compile-2.6!skip
plugins/modules/k8s_cp.py compile-2.7!skip
plugins/modules/k8s_cp.py import-2.6!skip
plugins/modules/k8s_cp.py import-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
plugins/module_utils/selector.py future-import-boilerplate!skip
plugins/module_utils/selector.py metaclass-boilerplate!skip
plugins/lookup/kustomize.py future-import-boilerplate!skip
plugins/lookup/kustomize.py metaclass-boilerplate!skip

View File

@@ -1,16 +1,243 @@
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart/templates/configmap.yaml yamllint!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/__init__.py compile-2.6!skip
plugins/module_utils/__init__.py compile-2.7!skip
plugins/module_utils/__init__.py compile-3.5!skip
plugins/module_utils/__init__.py future-import-boilerplate!skip
plugins/module_utils/__init__.py import-2.6!skip
plugins/module_utils/__init__.py import-2.7!skip
plugins/module_utils/__init__.py import-3.5!skip
plugins/module_utils/__init__.py metaclass-boilerplate!skip
plugins/module_utils/ansiblemodule.py compile-2.6!skip
plugins/module_utils/ansiblemodule.py compile-2.7!skip
plugins/module_utils/ansiblemodule.py compile-3.5!skip
plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
plugins/module_utils/ansiblemodule.py import-2.6!skip
plugins/module_utils/ansiblemodule.py import-2.7!skip
plugins/module_utils/ansiblemodule.py import-3.5!skip
plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
plugins/module_utils/apply.py compile-2.6!skip
plugins/module_utils/apply.py compile-2.7!skip
plugins/module_utils/apply.py compile-3.5!skip
plugins/module_utils/apply.py future-import-boilerplate!skip
plugins/module_utils/apply.py import-2.6!skip
plugins/module_utils/apply.py import-2.7!skip
plugins/module_utils/apply.py import-3.5!skip
plugins/module_utils/apply.py metaclass-boilerplate!skip
plugins/module_utils/args_common.py compile-2.6!skip
plugins/module_utils/args_common.py compile-2.7!skip
plugins/module_utils/args_common.py compile-3.5!skip
plugins/module_utils/args_common.py future-import-boilerplate!skip
plugins/module_utils/args_common.py import-2.6!skip
plugins/module_utils/args_common.py import-2.7!skip
plugins/module_utils/args_common.py import-3.5!skip
plugins/module_utils/args_common.py metaclass-boilerplate!skip
plugins/module_utils/client/discovery.py future-import-boilerplate!skip
plugins/module_utils/client/discovery.py import-2.6!skip
plugins/module_utils/client/discovery.py import-2.7!skip
plugins/module_utils/client/discovery.py import-3.5!skip
plugins/module_utils/client/discovery.py import-3.6!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.8!skip
plugins/module_utils/client/discovery.py import-3.9!skip
plugins/module_utils/client/discovery.py import-3.10!skip
plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py shebang
molecule/default/roles/k8scopy/library/k8s_create_file.py shebang
plugins/module_utils/client/resource.py import-2.6!skip
plugins/module_utils/client/resource.py import-2.7!skip
plugins/module_utils/client/resource.py import-3.5!skip
plugins/module_utils/client/resource.py import-3.6!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.8!skip
plugins/module_utils/client/resource.py import-3.9!skip
plugins/module_utils/client/resource.py import-3.10!skip
plugins/module_utils/common.py compile-2.6!skip
plugins/module_utils/common.py compile-2.7!skip
plugins/module_utils/common.py compile-3.5!skip
plugins/module_utils/common.py future-import-boilerplate!skip
plugins/module_utils/common.py import-2.6!skip
plugins/module_utils/common.py import-2.7!skip
plugins/module_utils/common.py import-3.5!skip
plugins/module_utils/common.py metaclass-boilerplate!skip
plugins/module_utils/exceptions.py compile-2.6!skip
plugins/module_utils/exceptions.py compile-2.7!skip
plugins/module_utils/exceptions.py compile-3.5!skip
plugins/module_utils/exceptions.py future-import-boilerplate!skip
plugins/module_utils/exceptions.py import-2.6!skip
plugins/module_utils/exceptions.py import-2.7!skip
plugins/module_utils/exceptions.py import-3.5!skip
plugins/module_utils/exceptions.py metaclass-boilerplate!skip
plugins/module_utils/hashes.py compile-2.6!skip
plugins/module_utils/hashes.py compile-2.7!skip
plugins/module_utils/hashes.py compile-3.5!skip
plugins/module_utils/hashes.py future-import-boilerplate!skip
plugins/module_utils/hashes.py import-2.6!skip
plugins/module_utils/hashes.py import-2.7!skip
plugins/module_utils/hashes.py import-3.5!skip
plugins/module_utils/hashes.py metaclass-boilerplate!skip
plugins/module_utils/helm.py compile-2.6!skip
plugins/module_utils/helm.py compile-2.7!skip
plugins/module_utils/helm.py compile-3.5!skip
plugins/module_utils/helm.py future-import-boilerplate!skip
plugins/module_utils/helm.py import-2.6!skip
plugins/module_utils/helm.py import-2.7!skip
plugins/module_utils/helm.py import-3.5!skip
plugins/module_utils/helm.py metaclass-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py compile-2.6!skip
plugins/module_utils/k8sdynamicclient.py compile-2.7!skip
plugins/module_utils/k8sdynamicclient.py compile-3.5!skip
plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py import-2.6!skip
plugins/module_utils/k8sdynamicclient.py import-2.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.5!skip
plugins/module_utils/k8sdynamicclient.py import-3.6!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.8!skip
plugins/module_utils/k8sdynamicclient.py import-3.9!skip
plugins/module_utils/k8sdynamicclient.py import-3.10!skip
plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
plugins/modules/__init__.py compile-2.6!skip
plugins/modules/__init__.py compile-2.7!skip
plugins/modules/__init__.py compile-3.5!skip
plugins/modules/__init__.py future-import-boilerplate!skip
plugins/modules/__init__.py import-2.6!skip
plugins/modules/__init__.py import-2.7!skip
plugins/modules/__init__.py import-3.5!skip
plugins/modules/__init__.py metaclass-boilerplate!skip
plugins/modules/helm.py compile-2.6!skip
plugins/modules/helm.py compile-2.7!skip
plugins/modules/helm.py compile-3.5!skip
plugins/modules/helm.py future-import-boilerplate!skip
plugins/modules/helm.py import-2.6!skip
plugins/modules/helm.py import-2.7!skip
plugins/modules/helm.py import-3.5!skip
plugins/modules/helm.py metaclass-boilerplate!skip
plugins/modules/helm_info.py compile-2.6!skip
plugins/modules/helm_info.py compile-2.7!skip
plugins/modules/helm_info.py compile-3.5!skip
plugins/modules/helm_info.py future-import-boilerplate!skip
plugins/modules/helm_info.py import-2.6!skip
plugins/modules/helm_info.py import-2.7!skip
plugins/modules/helm_info.py import-3.5!skip
plugins/modules/helm_info.py metaclass-boilerplate!skip
plugins/modules/helm_plugin.py compile-2.6!skip
plugins/modules/helm_plugin.py compile-2.7!skip
plugins/modules/helm_plugin.py compile-3.5!skip
plugins/modules/helm_plugin.py future-import-boilerplate!skip
plugins/modules/helm_plugin.py import-2.6!skip
plugins/modules/helm_plugin.py import-2.7!skip
plugins/modules/helm_plugin.py import-3.5!skip
plugins/modules/helm_plugin.py metaclass-boilerplate!skip
plugins/modules/helm_plugin_info.py compile-2.6!skip
plugins/modules/helm_plugin_info.py compile-2.7!skip
plugins/modules/helm_plugin_info.py compile-3.5!skip
plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
plugins/modules/helm_plugin_info.py import-2.6!skip
plugins/modules/helm_plugin_info.py import-2.7!skip
plugins/modules/helm_plugin_info.py import-3.5!skip
plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
plugins/modules/helm_repository.py compile-2.6!skip
plugins/modules/helm_repository.py compile-2.7!skip
plugins/modules/helm_repository.py compile-3.5!skip
plugins/modules/helm_repository.py future-import-boilerplate!skip
plugins/modules/helm_repository.py import-2.6!skip
plugins/modules/helm_repository.py import-2.7!skip
plugins/modules/helm_repository.py import-3.5!skip
plugins/modules/helm_repository.py metaclass-boilerplate!skip
plugins/modules/helm_template.py compile-2.6!skip
plugins/modules/helm_template.py compile-2.7!skip
plugins/modules/helm_template.py compile-3.5!skip
plugins/modules/helm_template.py future-import-boilerplate!skip
plugins/modules/helm_template.py import-2.6!skip
plugins/modules/helm_template.py import-2.7!skip
plugins/modules/helm_template.py import-3.5!skip
plugins/modules/helm_template.py metaclass-boilerplate!skip
plugins/modules/k8s.py compile-2.6!skip
plugins/modules/k8s.py compile-2.7!skip
plugins/modules/k8s.py compile-3.5!skip
plugins/modules/k8s.py future-import-boilerplate!skip
plugins/modules/k8s.py import-2.6!skip
plugins/modules/k8s.py import-2.7!skip
plugins/modules/k8s.py import-3.5!skip
plugins/modules/k8s.py metaclass-boilerplate!skip
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_cluster_info.py compile-2.6!skip
plugins/modules/k8s_cluster_info.py compile-2.7!skip
plugins/modules/k8s_cluster_info.py compile-3.5!skip
plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
plugins/modules/k8s_cluster_info.py import-2.6!skip
plugins/modules/k8s_cluster_info.py import-2.7!skip
plugins/modules/k8s_cluster_info.py import-3.5!skip
plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
plugins/modules/k8s_exec.py compile-2.6!skip
plugins/modules/k8s_exec.py compile-2.7!skip
plugins/modules/k8s_exec.py compile-3.5!skip
plugins/modules/k8s_exec.py future-import-boilerplate!skip
plugins/modules/k8s_exec.py import-2.6!skip
plugins/modules/k8s_exec.py import-2.7!skip
plugins/modules/k8s_exec.py import-3.5!skip
plugins/modules/k8s_exec.py metaclass-boilerplate!skip
plugins/modules/k8s_info.py compile-2.6!skip
plugins/modules/k8s_info.py compile-2.7!skip
plugins/modules/k8s_info.py compile-3.5!skip
plugins/modules/k8s_info.py future-import-boilerplate!skip
plugins/modules/k8s_info.py import-2.6!skip
plugins/modules/k8s_info.py import-2.7!skip
plugins/modules/k8s_info.py import-3.5!skip
plugins/modules/k8s_info.py metaclass-boilerplate!skip
plugins/modules/k8s_json_patch.py compile-2.6!skip
plugins/modules/k8s_json_patch.py compile-2.7!skip
plugins/modules/k8s_json_patch.py compile-3.5!skip
plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
plugins/modules/k8s_json_patch.py import-2.6!skip
plugins/modules/k8s_json_patch.py import-2.7!skip
plugins/modules/k8s_json_patch.py import-3.5!skip
plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
plugins/modules/k8s_log.py compile-2.6!skip
plugins/modules/k8s_log.py compile-2.7!skip
plugins/modules/k8s_log.py compile-3.5!skip
plugins/modules/k8s_log.py future-import-boilerplate!skip
plugins/modules/k8s_log.py import-2.6!skip
plugins/modules/k8s_log.py import-2.7!skip
plugins/modules/k8s_log.py import-3.5!skip
plugins/modules/k8s_log.py metaclass-boilerplate!skip
plugins/modules/k8s_rollback.py compile-2.6!skip
plugins/modules/k8s_rollback.py compile-2.7!skip
plugins/modules/k8s_rollback.py compile-3.5!skip
plugins/modules/k8s_rollback.py future-import-boilerplate!skip
plugins/modules/k8s_rollback.py import-2.6!skip
plugins/modules/k8s_rollback.py import-2.7!skip
plugins/modules/k8s_rollback.py import-3.5!skip
plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py compile-2.6!skip
plugins/modules/k8s_scale.py compile-2.7!skip
plugins/modules/k8s_scale.py compile-3.5!skip
plugins/modules/k8s_scale.py future-import-boilerplate!skip
plugins/modules/k8s_scale.py import-2.6!skip
plugins/modules/k8s_scale.py import-2.7!skip
plugins/modules/k8s_scale.py import-3.5!skip
plugins/modules/k8s_scale.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py compile-2.6!skip
plugins/modules/k8s_service.py compile-2.7!skip
plugins/modules/k8s_service.py compile-3.5!skip
plugins/modules/k8s_service.py future-import-boilerplate!skip
plugins/modules/k8s_service.py import-2.6!skip
plugins/modules/k8s_service.py import-2.7!skip
plugins/modules/k8s_service.py import-3.5!skip
plugins/modules/k8s_service.py metaclass-boilerplate!skip
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py validate-modules:return-syntax-error
tests/sanity/refresh_ignore_files shebang!skip
plugins/modules/k8s_cp.py compile-2.6!skip
plugins/modules/k8s_cp.py compile-2.7!skip
plugins/modules/k8s_cp.py import-2.6!skip
plugins/modules/k8s_cp.py import-2.7!skip
plugins/module_utils/selector.py future-import-boilerplate!skip
plugins/module_utils/selector.py metaclass-boilerplate!skip

View File

@@ -0,0 +1,189 @@
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart/templates/configmap.yaml yamllint!skip
plugins/module_utils/__init__.py compile-2.7!skip
plugins/module_utils/__init__.py compile-3.5!skip
plugins/module_utils/__init__.py future-import-boilerplate!skip
plugins/module_utils/__init__.py import-2.7!skip
plugins/module_utils/__init__.py import-3.5!skip
plugins/module_utils/__init__.py metaclass-boilerplate!skip
plugins/module_utils/ansiblemodule.py compile-2.7!skip
plugins/module_utils/ansiblemodule.py compile-3.5!skip
plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
plugins/module_utils/ansiblemodule.py import-2.7!skip
plugins/module_utils/ansiblemodule.py import-3.5!skip
plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
plugins/module_utils/apply.py compile-2.7!skip
plugins/module_utils/apply.py compile-3.5!skip
plugins/module_utils/apply.py future-import-boilerplate!skip
plugins/module_utils/apply.py import-2.7!skip
plugins/module_utils/apply.py import-3.5!skip
plugins/module_utils/apply.py metaclass-boilerplate!skip
plugins/module_utils/args_common.py compile-2.7!skip
plugins/module_utils/args_common.py compile-3.5!skip
plugins/module_utils/args_common.py future-import-boilerplate!skip
plugins/module_utils/args_common.py import-2.7!skip
plugins/module_utils/args_common.py import-3.5!skip
plugins/module_utils/args_common.py metaclass-boilerplate!skip
plugins/module_utils/client/discovery.py future-import-boilerplate!skip
plugins/module_utils/client/discovery.py import-2.7!skip
plugins/module_utils/client/discovery.py import-3.5!skip
plugins/module_utils/client/discovery.py import-3.6!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.8!skip
plugins/module_utils/client/discovery.py import-3.9!skip
plugins/module_utils/client/discovery.py import-3.10!skip
plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py shebang
molecule/default/roles/k8scopy/library/k8s_create_file.py shebang
plugins/module_utils/client/resource.py import-2.7!skip
plugins/module_utils/client/resource.py import-3.5!skip
plugins/module_utils/client/resource.py import-3.6!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.8!skip
plugins/module_utils/client/resource.py import-3.9!skip
plugins/module_utils/client/resource.py import-3.10!skip
plugins/module_utils/common.py compile-2.7!skip
plugins/module_utils/common.py compile-3.5!skip
plugins/module_utils/common.py future-import-boilerplate!skip
plugins/module_utils/common.py import-2.7!skip
plugins/module_utils/common.py import-3.5!skip
plugins/module_utils/common.py metaclass-boilerplate!skip
plugins/module_utils/exceptions.py compile-2.7!skip
plugins/module_utils/exceptions.py compile-3.5!skip
plugins/module_utils/exceptions.py future-import-boilerplate!skip
plugins/module_utils/exceptions.py import-2.7!skip
plugins/module_utils/exceptions.py import-3.5!skip
plugins/module_utils/exceptions.py metaclass-boilerplate!skip
plugins/module_utils/hashes.py compile-2.7!skip
plugins/module_utils/hashes.py compile-3.5!skip
plugins/module_utils/hashes.py future-import-boilerplate!skip
plugins/module_utils/hashes.py import-2.7!skip
plugins/module_utils/hashes.py import-3.5!skip
plugins/module_utils/hashes.py metaclass-boilerplate!skip
plugins/module_utils/helm.py compile-2.7!skip
plugins/module_utils/helm.py compile-3.5!skip
plugins/module_utils/helm.py future-import-boilerplate!skip
plugins/module_utils/helm.py import-2.7!skip
plugins/module_utils/helm.py import-3.5!skip
plugins/module_utils/helm.py metaclass-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py compile-2.7!skip
plugins/module_utils/k8sdynamicclient.py compile-3.5!skip
plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py import-2.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.5!skip
plugins/module_utils/k8sdynamicclient.py import-3.6!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.8!skip
plugins/module_utils/k8sdynamicclient.py import-3.9!skip
plugins/module_utils/k8sdynamicclient.py import-3.10!skip
plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
plugins/modules/__init__.py compile-2.7!skip
plugins/modules/__init__.py compile-3.5!skip
plugins/modules/__init__.py future-import-boilerplate!skip
plugins/modules/__init__.py import-2.7!skip
plugins/modules/__init__.py import-3.5!skip
plugins/modules/__init__.py metaclass-boilerplate!skip
plugins/modules/helm.py compile-2.7!skip
plugins/modules/helm.py compile-3.5!skip
plugins/modules/helm.py future-import-boilerplate!skip
plugins/modules/helm.py import-2.7!skip
plugins/modules/helm.py import-3.5!skip
plugins/modules/helm.py metaclass-boilerplate!skip
plugins/modules/helm_info.py compile-2.7!skip
plugins/modules/helm_info.py compile-3.5!skip
plugins/modules/helm_info.py future-import-boilerplate!skip
plugins/modules/helm_info.py import-2.7!skip
plugins/modules/helm_info.py import-3.5!skip
plugins/modules/helm_info.py metaclass-boilerplate!skip
plugins/modules/helm_plugin.py compile-2.7!skip
plugins/modules/helm_plugin.py compile-3.5!skip
plugins/modules/helm_plugin.py future-import-boilerplate!skip
plugins/modules/helm_plugin.py import-2.7!skip
plugins/modules/helm_plugin.py import-3.5!skip
plugins/modules/helm_plugin.py metaclass-boilerplate!skip
plugins/modules/helm_plugin_info.py compile-2.7!skip
plugins/modules/helm_plugin_info.py compile-3.5!skip
plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
plugins/modules/helm_plugin_info.py import-2.7!skip
plugins/modules/helm_plugin_info.py import-3.5!skip
plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
plugins/modules/helm_repository.py compile-2.7!skip
plugins/modules/helm_repository.py compile-3.5!skip
plugins/modules/helm_repository.py future-import-boilerplate!skip
plugins/modules/helm_repository.py import-2.7!skip
plugins/modules/helm_repository.py import-3.5!skip
plugins/modules/helm_repository.py metaclass-boilerplate!skip
plugins/modules/helm_template.py compile-2.7!skip
plugins/modules/helm_template.py compile-3.5!skip
plugins/modules/helm_template.py future-import-boilerplate!skip
plugins/modules/helm_template.py import-2.7!skip
plugins/modules/helm_template.py import-3.5!skip
plugins/modules/helm_template.py metaclass-boilerplate!skip
plugins/modules/k8s.py compile-2.7!skip
plugins/modules/k8s.py compile-3.5!skip
plugins/modules/k8s.py future-import-boilerplate!skip
plugins/modules/k8s.py import-2.7!skip
plugins/modules/k8s.py import-3.5!skip
plugins/modules/k8s.py metaclass-boilerplate!skip
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s.py validate-modules:return-syntax-error
plugins/modules/k8s_cluster_info.py compile-2.7!skip
plugins/modules/k8s_cluster_info.py compile-3.5!skip
plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
plugins/modules/k8s_cluster_info.py import-2.7!skip
plugins/modules/k8s_cluster_info.py import-3.5!skip
plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
plugins/modules/k8s_exec.py compile-2.7!skip
plugins/modules/k8s_exec.py compile-3.5!skip
plugins/modules/k8s_exec.py future-import-boilerplate!skip
plugins/modules/k8s_exec.py import-2.7!skip
plugins/modules/k8s_exec.py import-3.5!skip
plugins/modules/k8s_exec.py metaclass-boilerplate!skip
plugins/modules/k8s_info.py compile-2.7!skip
plugins/modules/k8s_info.py compile-3.5!skip
plugins/modules/k8s_info.py future-import-boilerplate!skip
plugins/modules/k8s_info.py import-2.7!skip
plugins/modules/k8s_info.py import-3.5!skip
plugins/modules/k8s_info.py metaclass-boilerplate!skip
plugins/modules/k8s_json_patch.py compile-2.7!skip
plugins/modules/k8s_json_patch.py compile-3.5!skip
plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
plugins/modules/k8s_json_patch.py import-2.7!skip
plugins/modules/k8s_json_patch.py import-3.5!skip
plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
plugins/modules/k8s_log.py compile-2.7!skip
plugins/modules/k8s_log.py compile-3.5!skip
plugins/modules/k8s_log.py future-import-boilerplate!skip
plugins/modules/k8s_log.py import-2.7!skip
plugins/modules/k8s_log.py import-3.5!skip
plugins/modules/k8s_log.py metaclass-boilerplate!skip
plugins/modules/k8s_rollback.py compile-2.7!skip
plugins/modules/k8s_rollback.py compile-3.5!skip
plugins/modules/k8s_rollback.py future-import-boilerplate!skip
plugins/modules/k8s_rollback.py import-2.7!skip
plugins/modules/k8s_rollback.py import-3.5!skip
plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py compile-2.7!skip
plugins/modules/k8s_scale.py compile-3.5!skip
plugins/modules/k8s_scale.py future-import-boilerplate!skip
plugins/modules/k8s_scale.py import-2.7!skip
plugins/modules/k8s_scale.py import-3.5!skip
plugins/modules/k8s_scale.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:return-syntax-error
plugins/modules/k8s_service.py compile-2.7!skip
plugins/modules/k8s_service.py compile-3.5!skip
plugins/modules/k8s_service.py future-import-boilerplate!skip
plugins/modules/k8s_service.py import-2.7!skip
plugins/modules/k8s_service.py import-3.5!skip
plugins/modules/k8s_service.py metaclass-boilerplate!skip
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py validate-modules:return-syntax-error
tests/sanity/refresh_ignore_files shebang!skip
plugins/modules/k8s_cp.py compile-2.7!skip
plugins/modules/k8s_cp.py import-2.7!skip
plugins/module_utils/selector.py future-import-boilerplate!skip
plugins/module_utils/selector.py metaclass-boilerplate!skip

View File

@@ -1,15 +1,243 @@
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
molecule/default/roles/helm/files/test-chart/templates/configmap.yaml yamllint!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/__init__.py compile-2.6!skip
plugins/module_utils/__init__.py compile-2.7!skip
plugins/module_utils/__init__.py compile-3.5!skip
plugins/module_utils/__init__.py future-import-boilerplate!skip
plugins/module_utils/__init__.py import-2.6!skip
plugins/module_utils/__init__.py import-2.7!skip
plugins/module_utils/__init__.py import-3.5!skip
plugins/module_utils/__init__.py metaclass-boilerplate!skip
plugins/module_utils/ansiblemodule.py compile-2.6!skip
plugins/module_utils/ansiblemodule.py compile-2.7!skip
plugins/module_utils/ansiblemodule.py compile-3.5!skip
plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
plugins/module_utils/ansiblemodule.py import-2.6!skip
plugins/module_utils/ansiblemodule.py import-2.7!skip
plugins/module_utils/ansiblemodule.py import-3.5!skip
plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
plugins/module_utils/apply.py compile-2.6!skip
plugins/module_utils/apply.py compile-2.7!skip
plugins/module_utils/apply.py compile-3.5!skip
plugins/module_utils/apply.py future-import-boilerplate!skip
plugins/module_utils/apply.py import-2.6!skip
plugins/module_utils/apply.py import-2.7!skip
plugins/module_utils/apply.py import-3.5!skip
plugins/module_utils/apply.py metaclass-boilerplate!skip
plugins/module_utils/args_common.py compile-2.6!skip
plugins/module_utils/args_common.py compile-2.7!skip
plugins/module_utils/args_common.py compile-3.5!skip
plugins/module_utils/args_common.py future-import-boilerplate!skip
plugins/module_utils/args_common.py import-2.6!skip
plugins/module_utils/args_common.py import-2.7!skip
plugins/module_utils/args_common.py import-3.5!skip
plugins/module_utils/args_common.py metaclass-boilerplate!skip
plugins/module_utils/client/discovery.py future-import-boilerplate!skip
plugins/module_utils/client/discovery.py import-2.6!skip
plugins/module_utils/client/discovery.py import-2.7!skip
plugins/module_utils/client/discovery.py import-3.5!skip
plugins/module_utils/client/discovery.py import-3.6!skip
plugins/module_utils/client/discovery.py import-3.7!skip
plugins/module_utils/client/discovery.py import-3.8!skip
plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
plugins/module_utils/client/resource.py import-2.6!skip
plugins/module_utils/client/resource.py import-2.7!skip
plugins/module_utils/client/resource.py import-3.5!skip
plugins/module_utils/client/resource.py import-3.6!skip
plugins/module_utils/client/resource.py import-3.7!skip
plugins/module_utils/client/resource.py import-3.8!skip
plugins/module_utils/common.py compile-2.6!skip
plugins/module_utils/common.py compile-2.7!skip
plugins/module_utils/common.py compile-3.5!skip
plugins/module_utils/common.py future-import-boilerplate!skip
plugins/module_utils/common.py import-2.6!skip
plugins/module_utils/common.py import-2.7!skip
plugins/module_utils/common.py import-3.5!skip
plugins/module_utils/common.py metaclass-boilerplate!skip
plugins/module_utils/exceptions.py compile-2.6!skip
plugins/module_utils/exceptions.py compile-2.7!skip
plugins/module_utils/exceptions.py compile-3.5!skip
plugins/module_utils/exceptions.py future-import-boilerplate!skip
plugins/module_utils/exceptions.py import-2.6!skip
plugins/module_utils/exceptions.py import-2.7!skip
plugins/module_utils/exceptions.py import-3.5!skip
plugins/module_utils/exceptions.py metaclass-boilerplate!skip
plugins/module_utils/hashes.py compile-2.6!skip
plugins/module_utils/hashes.py compile-2.7!skip
plugins/module_utils/hashes.py compile-3.5!skip
plugins/module_utils/hashes.py future-import-boilerplate!skip
plugins/module_utils/hashes.py import-2.6!skip
plugins/module_utils/hashes.py import-2.7!skip
plugins/module_utils/hashes.py import-3.5!skip
plugins/module_utils/hashes.py metaclass-boilerplate!skip
plugins/module_utils/helm.py compile-2.6!skip
plugins/module_utils/helm.py compile-2.7!skip
plugins/module_utils/helm.py compile-3.5!skip
plugins/module_utils/helm.py future-import-boilerplate!skip
plugins/module_utils/helm.py import-2.6!skip
plugins/module_utils/helm.py import-2.7!skip
plugins/module_utils/helm.py import-3.5!skip
plugins/module_utils/helm.py metaclass-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py compile-2.6!skip
plugins/module_utils/k8sdynamicclient.py compile-2.7!skip
plugins/module_utils/k8sdynamicclient.py compile-3.5!skip
plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
plugins/module_utils/k8sdynamicclient.py import-2.6!skip
plugins/module_utils/k8sdynamicclient.py import-2.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.5!skip
plugins/module_utils/k8sdynamicclient.py import-3.6!skip
plugins/module_utils/k8sdynamicclient.py import-3.7!skip
plugins/module_utils/k8sdynamicclient.py import-3.8!skip
plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
plugins/modules/__init__.py compile-2.6!skip
plugins/modules/__init__.py compile-2.7!skip
plugins/modules/__init__.py compile-3.5!skip
plugins/modules/__init__.py future-import-boilerplate!skip
plugins/modules/__init__.py import-2.6!skip
plugins/modules/__init__.py import-2.7!skip
plugins/modules/__init__.py import-3.5!skip
plugins/modules/__init__.py metaclass-boilerplate!skip
plugins/modules/helm.py compile-2.6!skip
plugins/modules/helm.py compile-2.7!skip
plugins/modules/helm.py compile-3.5!skip
plugins/modules/helm.py future-import-boilerplate!skip
plugins/modules/helm.py import-2.6!skip
plugins/modules/helm.py import-2.7!skip
plugins/modules/helm.py import-3.5!skip
plugins/modules/helm.py metaclass-boilerplate!skip
plugins/modules/helm_info.py compile-2.6!skip
plugins/modules/helm_info.py compile-2.7!skip
plugins/modules/helm_info.py compile-3.5!skip
plugins/modules/helm_info.py future-import-boilerplate!skip
plugins/modules/helm_info.py import-2.6!skip
plugins/modules/helm_info.py import-2.7!skip
plugins/modules/helm_info.py import-3.5!skip
plugins/modules/helm_info.py metaclass-boilerplate!skip
plugins/modules/helm_plugin.py compile-2.6!skip
plugins/modules/helm_plugin.py compile-2.7!skip
plugins/modules/helm_plugin.py compile-3.5!skip
plugins/modules/helm_plugin.py future-import-boilerplate!skip
plugins/modules/helm_plugin.py import-2.6!skip
plugins/modules/helm_plugin.py import-2.7!skip
plugins/modules/helm_plugin.py import-3.5!skip
plugins/modules/helm_plugin.py metaclass-boilerplate!skip
plugins/modules/helm_plugin_info.py compile-2.6!skip
plugins/modules/helm_plugin_info.py compile-2.7!skip
plugins/modules/helm_plugin_info.py compile-3.5!skip
plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
plugins/modules/helm_plugin_info.py import-2.6!skip
plugins/modules/helm_plugin_info.py import-2.7!skip
plugins/modules/helm_plugin_info.py import-3.5!skip
plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
plugins/modules/helm_repository.py compile-2.6!skip
plugins/modules/helm_repository.py compile-2.7!skip
plugins/modules/helm_repository.py compile-3.5!skip
plugins/modules/helm_repository.py future-import-boilerplate!skip
plugins/modules/helm_repository.py import-2.6!skip
plugins/modules/helm_repository.py import-2.7!skip
plugins/modules/helm_repository.py import-3.5!skip
plugins/modules/helm_repository.py metaclass-boilerplate!skip
plugins/modules/helm_template.py compile-2.6!skip
plugins/modules/helm_template.py compile-2.7!skip
plugins/modules/helm_template.py compile-3.5!skip
plugins/modules/helm_template.py future-import-boilerplate!skip
plugins/modules/helm_template.py import-2.6!skip
plugins/modules/helm_template.py import-2.7!skip
plugins/modules/helm_template.py import-3.5!skip
plugins/modules/helm_template.py metaclass-boilerplate!skip
plugins/modules/k8s.py compile-2.6!skip
plugins/modules/k8s.py compile-2.7!skip
plugins/modules/k8s.py compile-3.5!skip
plugins/modules/k8s.py future-import-boilerplate!skip
plugins/modules/k8s.py import-2.6!skip
plugins/modules/k8s.py import-2.7!skip
plugins/modules/k8s.py import-3.5!skip
plugins/modules/k8s.py metaclass-boilerplate!skip
plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_cluster_info.py compile-2.6!skip
plugins/modules/k8s_cluster_info.py compile-2.7!skip
plugins/modules/k8s_cluster_info.py compile-3.5!skip
plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
plugins/modules/k8s_cluster_info.py import-2.6!skip
plugins/modules/k8s_cluster_info.py import-2.7!skip
plugins/modules/k8s_cluster_info.py import-3.5!skip
plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
plugins/modules/k8s_exec.py compile-2.6!skip
plugins/modules/k8s_exec.py compile-2.7!skip
plugins/modules/k8s_exec.py compile-3.5!skip
plugins/modules/k8s_exec.py future-import-boilerplate!skip
plugins/modules/k8s_exec.py import-2.6!skip
plugins/modules/k8s_exec.py import-2.7!skip
plugins/modules/k8s_exec.py import-3.5!skip
plugins/modules/k8s_exec.py metaclass-boilerplate!skip
plugins/modules/k8s_info.py compile-2.6!skip
plugins/modules/k8s_info.py compile-2.7!skip
plugins/modules/k8s_info.py compile-3.5!skip
plugins/modules/k8s_info.py future-import-boilerplate!skip
plugins/modules/k8s_info.py import-2.6!skip
plugins/modules/k8s_info.py import-2.7!skip
plugins/modules/k8s_info.py import-3.5!skip
plugins/modules/k8s_info.py metaclass-boilerplate!skip
plugins/modules/k8s_json_patch.py compile-2.6!skip
plugins/modules/k8s_json_patch.py compile-2.7!skip
plugins/modules/k8s_json_patch.py compile-3.5!skip
plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
plugins/modules/k8s_json_patch.py import-2.6!skip
plugins/modules/k8s_json_patch.py import-2.7!skip
plugins/modules/k8s_json_patch.py import-3.5!skip
plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
plugins/modules/k8s_log.py compile-2.6!skip
plugins/modules/k8s_log.py compile-2.7!skip
plugins/modules/k8s_log.py compile-3.5!skip
plugins/modules/k8s_log.py future-import-boilerplate!skip
plugins/modules/k8s_log.py import-2.6!skip
plugins/modules/k8s_log.py import-2.7!skip
plugins/modules/k8s_log.py import-3.5!skip
plugins/modules/k8s_log.py metaclass-boilerplate!skip
plugins/modules/k8s_rollback.py compile-2.6!skip
plugins/modules/k8s_rollback.py compile-2.7!skip
plugins/modules/k8s_rollback.py compile-3.5!skip
plugins/modules/k8s_rollback.py future-import-boilerplate!skip
plugins/modules/k8s_rollback.py import-2.6!skip
plugins/modules/k8s_rollback.py import-2.7!skip
plugins/modules/k8s_rollback.py import-3.5!skip
plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py compile-2.6!skip
plugins/modules/k8s_scale.py compile-2.7!skip
plugins/modules/k8s_scale.py compile-3.5!skip
plugins/modules/k8s_scale.py future-import-boilerplate!skip
plugins/modules/k8s_scale.py import-2.6!skip
plugins/modules/k8s_scale.py import-2.7!skip
plugins/modules/k8s_scale.py import-3.5!skip
plugins/modules/k8s_scale.py metaclass-boilerplate!skip
plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc
plugins/modules/k8s_service.py compile-2.6!skip
plugins/modules/k8s_service.py compile-2.7!skip
plugins/modules/k8s_service.py compile-3.5!skip
plugins/modules/k8s_service.py future-import-boilerplate!skip
plugins/modules/k8s_service.py import-2.6!skip
plugins/modules/k8s_service.py import-2.7!skip
plugins/modules/k8s_service.py import-3.5!skip
plugins/modules/k8s_service.py metaclass-boilerplate!skip
plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
molecule/default/files/deployment.yaml yamllint!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py shebang
molecule/default/roles/k8scopy/library/k8s_create_file.py shebang
plugins/modules/k8s_cp.py compile-2.6!skip
plugins/modules/k8s_cp.py compile-2.7!skip
plugins/modules/k8s_cp.py import-2.6!skip
plugins/modules/k8s_cp.py import-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.6!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-2.7!skip
molecule/default/roles/k8scopy/library/kubectl_file_compare.py compile-3.5!skip
tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
plugins/module_utils/selector.py future-import-boilerplate!skip
plugins/module_utils/selector.py metaclass-boilerplate!skip
plugins/lookup/kustomize.py future-import-boilerplate!skip
plugins/lookup/kustomize.py metaclass-boilerplate!skip

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
from pathlib import Path
target_dir = Path('.')
ignore_dir = target_dir / "tests" / "sanity"
module_dir = target_dir / "plugins" / "modules"
module_utils_dir = target_dir / "plugins" / "module_utils"
ignore_dir.mkdir(parents=True, exist_ok=True)
skip_list = [
"compile-2.6!skip", # Py3.8+
"compile-2.7!skip", # Py3.8+
"compile-3.5!skip", # Py3.8+
"import-2.6!skip", # Py3.8+
"import-2.7!skip", # Py3.8+
"import-3.5!skip", # Py3.8+
"future-import-boilerplate!skip", # Py2 only
"metaclass-boilerplate!skip", # Py2 only
]
for version in ["2.9", "2.10", "2.11", "2.12"]:
ignore_file = ignore_dir / f"ignore-{version}.txt"
ignore_content = ignore_file.read_text().split("\n")
ignore_content.append(f"tests/sanity/refresh_ignore_files shebang!skip")
for f in module_dir.glob("*.py"):
if f.is_symlink():
continue
for test in skip_list:
ignore_content.append(f"{f} {test}")
for f in module_utils_dir.glob("*.py"):
if f.is_symlink():
continue
for test in skip_list:
ignore_content.append(f"{f} {test}")
ignore_file = ignore_dir / f"ignore-{version}.txt"
ignore_file.write_text("\n".join(sorted(set(ignore_content))).lstrip("\n"))

View File

@@ -41,7 +41,7 @@ def test_write_temp_kubeconfig_server_only():
file_name = write_temp_kubeconfig("ff")
try:
with open(file_name, "r") as fd:
content = yaml.load(fd)
content = yaml.safe_load(fd)
finally:
os.remove(file_name)
@@ -60,7 +60,7 @@ def test_write_temp_kubeconfig_server_inscure_certs():
file_name = write_temp_kubeconfig("ff", False, "my-certificate")
try:
with open(file_name, "r") as fd:
content = yaml.load(fd)
content = yaml.safe_load(fd)
finally:
os.remove(file_name)

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