summaryrefslogtreecommitdiffstats
path: root/ansible_collections/kubernetes/core
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
commit66cec45960ce1d9c794e9399de15c138acb18aed (patch)
tree59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/kubernetes/core
parentInitial commit. (diff)
downloadansible-upstream.tar.xz
ansible-upstream.zip
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/kubernetes/core')
-rw-r--r--ansible_collections/kubernetes/core/.github/patchback.yml4
-rw-r--r--ansible_collections/kubernetes/core/.github/stale.yml60
-rw-r--r--ansible_collections/kubernetes/core/.gitignore22
-rw-r--r--ansible_collections/kubernetes/core/.yamllint20
-rw-r--r--ansible_collections/kubernetes/core/CHANGELOG.rst525
-rw-r--r--ansible_collections/kubernetes/core/CONTRIBUTING.md80
-rw-r--r--ansible_collections/kubernetes/core/FILES.json4191
-rw-r--r--ansible_collections/kubernetes/core/LICENSE674
-rw-r--r--ansible_collections/kubernetes/core/MANIFEST.json43
-rw-r--r--ansible_collections/kubernetes/core/Makefile28
-rw-r--r--ansible_collections/kubernetes/core/PSF-license.txt48
-rw-r--r--ansible_collections/kubernetes/core/README.md242
-rw-r--r--ansible_collections/kubernetes/core/bindep.txt3
-rw-r--r--ansible_collections/kubernetes/core/changelogs/changelog.yaml764
-rw-r--r--ansible_collections/kubernetes/core/changelogs/config.yaml30
-rw-r--r--ansible_collections/kubernetes/core/codecov.yml8
-rw-r--r--ansible_collections/kubernetes/core/docs/ansible_turbo_mode.rst147
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/extra-docs.yml5
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst51
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst88
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst12
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst175
-rw-r--r--ansible_collections/kubernetes/core/docs/docsite/rst/scenario_guide.rst18
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_info_module.rst381
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_module.rst877
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_info_module.rst300
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_module.rst346
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_pull_module.rst467
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_repository_module.rst291
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.helm_template_module.rst310
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cluster_info_module.rst709
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cp_module.rst589
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_drain_module.rst616
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_exec_module.rst590
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_info_module.rst801
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_inventory.rst359
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_json_patch_module.rst755
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_log_module.rst579
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_lookup.rst557
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_module.rst1268
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_rollback_module.rst602
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_scale_module.rst803
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_service_module.rst713
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_taint_module.rst660
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.kubectl_connection.rst361
-rw-r--r--ansible_collections/kubernetes/core/docs/kubernetes.core.kustomize_lookup.rst251
-rw-r--r--ansible_collections/kubernetes/core/meta/runtime.yml46
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/helm.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/helm_info.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/helm_plugin.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/helm_plugin_info.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/helm_repository.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_cluster_info.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_cp.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_drain.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_exec.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_info.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_log.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_rollback.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_scale.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/k8s_service.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/action/ks8_json_patch.py406
-rw-r--r--ansible_collections/kubernetes/core/plugins/connection/kubectl.py444
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/helm_common_options.py61
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_auth_options.py140
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_delete_options.py52
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_name_options.py53
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_resource_options.py35
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_scale_options.py50
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_state_options.py31
-rw-r--r--ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_wait_options.py68
-rw-r--r--ansible_collections/kubernetes/core/plugins/filter/k8s.py31
-rw-r--r--ansible_collections/kubernetes/core/plugins/filter/k8s_config_resource_name.yml36
-rw-r--r--ansible_collections/kubernetes/core/plugins/inventory/k8s.py464
-rw-r--r--ansible_collections/kubernetes/core/plugins/lookup/k8s.py304
-rw-r--r--ansible_collections/kubernetes/core/plugins/lookup/kustomize.py131
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/_version.py344
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/ansiblemodule.py25
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/apply.py316
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/args_common.py98
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/client/discovery.py216
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/client/resource.py60
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/common.py1505
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/copy.py444
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/exceptions.py22
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/hashes.py78
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/helm.py303
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/helm_args_common.py42
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/client.py368
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/core.py175
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/exceptions.py12
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/resource.py134
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/runner.py199
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/service.py496
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8s/waiter.py238
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/k8sdynamicclient.py50
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/selector.py79
-rw-r--r--ansible_collections/kubernetes/core/plugins/module_utils/version.py18
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm.py924
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_info.py252
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_plugin.py322
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_plugin_info.py133
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_pull.py302
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_repository.py340
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/helm_template.py359
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s.py479
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_cluster_info.py232
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_cp.py225
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_drain.py513
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_exec.py254
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_info.py217
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_json_patch.py297
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_log.py362
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_rollback.py274
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_scale.py422
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_service.py265
-rw-r--r--ansible_collections/kubernetes/core/plugins/modules/k8s_taint.py313
-rw-r--r--ansible_collections/kubernetes/core/requirements.txt3
-rw-r--r--ansible_collections/kubernetes/core/setup.cfg4
-rw-r--r--ansible_collections/kubernetes/core/test-requirements.txt7
-rw-r--r--ansible_collections/kubernetes/core/tests/config.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/defaults/main.yml26
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/Chart.yaml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml7
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/Chart.yaml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/Chart.yaml10
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/values.yaml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/Chart.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml7
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/Chart.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/templates/configmap.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/Chart.yaml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/crds/crd.yaml21
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/files/values.yaml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/library/helm_test_version.py96
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/meta/main.yml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/install.yml15
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/main.yml7
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/run_test.yml43
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_crds.yml99
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_not_installed.yml15
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_uninstall.yml80
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_read_envvars.yml12
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_up_dep.yml160
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart.yml426
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_local_path.yml111
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_repository.yml22
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_url.yml8
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/Chart.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/meta/main.yml4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/tasks/main.yml259
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/defaults/main.yml7
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_in_memory_kubeconfig.yml9
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_cacert.yml76
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_validate_certs.yml67
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/main.yml19
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/tests_helm_auth.yml197
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/files/sample_plugin/plugin.yaml11
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/tasks/main.yml165
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/tasks/main.yml232
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/aliases5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/tasks/main.yml80
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/tasks/main.yml109
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/install_helm/aliases1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/install_helm/defaults/main.yml4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/install_helm/tasks/main.yml15
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/create_resources.yml46
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/delete_resources.yml30
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/play.yml90
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/test.inventory_k8s.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/vars/main.yml38
-rwxr-xr-xansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/runme.sh29
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/tasks/main.yml22
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/tasks/main.yml69
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/defaults/main.yml42
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/main.yml783
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml24
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/defaults/main.yml10
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/check_mode.yml32
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/main.yml19
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/tasks/main.yml24
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/defaults/main.yml16
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/archive.tarbin0 -> 10240 bytes
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/collection.txt1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/module.txt1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/file.txt1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/teams/ansible.txt2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_file.txt1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_zip_file.txt.gzbin0 -> 89 bytes
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/k8s_create_file.py93
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/kubectl_file_compare.py247
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/meta/main.yml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/main.yml36
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_check_mode.yml142
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_directory.yml135
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml69
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_file.yml199
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_item_with_space_in_its_name.yml120
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_large_file.yml101
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_multi_container_pod.yml67
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/templates/pods_definition.j245
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/crd-resource.yml21
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/setup-crd.yml53
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/tasks/main.yml61
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/defaults/main.yml25
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/tasks/main.yml129
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/tasks/main.yml148
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/templates/pod.j214
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/tasks/main.yml367
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/tasks/main.yml94
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/defaults/main.yml10
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/tasks/main.yml509
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/aliases1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/tasks/main.yml236
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/tasks/main.yml188
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/defaults/main.yml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/api-server-caching.yml91
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/main.yml5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/wait.yml238
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/tasks/main.yml172
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/tasks/main.yml652
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/tasks/main.yml141
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/tasks/main.yml247
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/defaults/main.yml64
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/meta/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/tasks/main.yml132
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/tasks/main.yml138
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/defaults/main.yml4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/tasks/main.yml121
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/tasks/main.yml301
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/defaults/main.yml42
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/files/deployment.yaml50
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/tasks/main.yml398
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/tasks/main.yml443
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/aliases4
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/defaults/main.yml3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/tasks/main.yml305
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/configmap.yml.j27
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_one.j216
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_three.j235
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_two.j216
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_with_bad_namespace.j216
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/aliases2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/tasks/main.yml220
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/tasks/main.yml232
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/aliases5
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/defaults/main.yml40
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/tasks/main.yml470
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/defaults/main.yml7
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/tasks/main.yml240
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/aliases3
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/defaults/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/meta/main.yml2
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/tasks/main.yml108
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/aliases1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/tasks/main.yml17
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/aliases1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/defaults/main.yml6
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py126
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/tasks/main.yml46
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/aliases1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/defaults/main.yml1
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/create.yml22
-rw-r--r--ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/main.yml13
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.10.txt616
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.11.txt592
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.12.txt32
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.13.txt32
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.14.txt32
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.15.txt35
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/ignore-2.9.txt613
-rw-r--r--ansible_collections/kubernetes/core/tests/sanity/refresh_ignore_files216
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/action/test_remove_omit.py104
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/conftest.py44
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/definitions.yml34
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/deployments.yml48
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/pods.yml63
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_apply.py490
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_client.py184
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_common.py35
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_core.py91
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_discoverer.py165
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_hashes.py68
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_helm.py454
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_marshal.py97
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_resource.py187
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_runner.py135
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_selector.py204
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_service.py292
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/module_utils/test_waiter.py122
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template.py172
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template_module.py103
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/modules/test_module_helm.py497
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/requirements.txt3
-rw-r--r--ansible_collections/kubernetes/core/tests/unit/utils/ansible_module_mock.py59
-rw-r--r--ansible_collections/kubernetes/core/tox.ini38
383 files changed, 58759 insertions, 0 deletions
diff --git a/ansible_collections/kubernetes/core/.github/patchback.yml b/ansible_collections/kubernetes/core/.github/patchback.yml
new file mode 100644
index 00000000..113fc529
--- /dev/null
+++ b/ansible_collections/kubernetes/core/.github/patchback.yml
@@ -0,0 +1,4 @@
+---
+backport_branch_prefix: patchback/backports/
+backport_label_prefix: backport-
+target_branch_prefix: stable-
diff --git a/ansible_collections/kubernetes/core/.github/stale.yml b/ansible_collections/kubernetes/core/.github/stale.yml
new file mode 100644
index 00000000..230cf78a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/.github/stale.yml
@@ -0,0 +1,60 @@
+---
+# Configuration for probot-stale - https://github.com/probot/stale
+
+# Number of days of inactivity before an Issue or Pull Request becomes stale
+daysUntilStale: 90
+
+# Number of days of inactivity before an Issue or Pull Request with the stale
+# label is closed. Set to false to disable. If disabled, issues still need to be
+# closed manually, but will remain marked as stale.
+daysUntilClose: 30
+
+# Only issues or pull requests with all of these labels are check if stale.
+# Defaults to `[]` (disabled)
+onlyLabels: []
+
+# Issues or Pull Requests with these labels will never be considered stale. Set
+# to `[]` to disable
+exemptLabels:
+ - security
+ - planned
+ - priority/critical
+ - lifecycle/frozen
+ - verified
+
+# Set to true to ignore issues in a project (defaults to false)
+exemptProjects: false
+
+# Set to true to ignore issues in a milestone (defaults to false)
+exemptMilestones: true
+
+# Set to true to ignore issues with an assignee (defaults to false)
+exemptAssignees: false
+
+# Label to use when marking as stale
+staleLabel: lifecycle/stale
+
+# Limit the number of actions per hour, from 1-30. Default is 30
+limitPerRun: 30
+
+pulls:
+ markComment: |-
+ PRs go stale after 90 days of inactivity.
+ If there is no further activity, the PR will be closed in another 30 days.
+
+ unmarkComment: >-
+ This pull request is no longer stale.
+
+ closeComment: >-
+ This pull request has been closed due to inactivity.
+
+issues:
+ markComment: |-
+ Issues go stale after 90 days of inactivity.
+ If there is no further activity, the issue will be closed in another 30 days.
+
+ unmarkComment: >-
+ This issue is no longer stale.
+
+ closeComment: >-
+ This issue has been closed due to inactivity.
diff --git a/ansible_collections/kubernetes/core/.gitignore b/ansible_collections/kubernetes/core/.gitignore
new file mode 100644
index 00000000..43300e50
--- /dev/null
+++ b/ansible_collections/kubernetes/core/.gitignore
@@ -0,0 +1,22 @@
+*.retry
+.idea
+*.log
+__pycache__/
+
+# Galaxy artifacts.
+*.tar.gz
+
+# Changelog cache files.
+changelogs/.plugin-cache.yaml
+
+# Temporary test files.
+tests/output
+tests/integration/cloud-config-*
+.cache
+
+# Helm charts
+tests/integration/*-chart-*.tgz
+
+# ansible-test generated file
+tests/integration/inventory
+tests/integration/*-*.yml
diff --git a/ansible_collections/kubernetes/core/.yamllint b/ansible_collections/kubernetes/core/.yamllint
new file mode 100644
index 00000000..bc74eedb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/.yamllint
@@ -0,0 +1,20 @@
+---
+extends: default
+
+rules:
+ braces:
+ max-spaces-inside: 1
+ level: error
+ brackets:
+ max-spaces-inside: 1
+ level: error
+ document-start: disable
+ line-length: disable
+ truthy: disable
+ indentation:
+ spaces: 2
+ indent-sequences: consistent
+ignore: |
+ .cache
+ .tox
+ tests/output
diff --git a/ansible_collections/kubernetes/core/CHANGELOG.rst b/ansible_collections/kubernetes/core/CHANGELOG.rst
new file mode 100644
index 00000000..bfb1a1b1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/CHANGELOG.rst
@@ -0,0 +1,525 @@
+===================================
+Kubernetes Collection Release Notes
+===================================
+
+.. contents:: Topics
+
+
+v2.4.0
+======
+
+Major Changes
+-------------
+
+- refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
+
+Minor Changes
+-------------
+
+- Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24 (https://github.com/ansible-collections/kubernetes.core/pull/520).
+- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
+- added ignore.txt for Ansible 2.14 devel branch.
+- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
+- helm - add support for -set-file, -set-json, -set and -set-string options when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
+- helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
+- helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
+- helm - add support for timeout cli parameter to allow setting Helm timeout independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
+- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
+- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
+- helm - when ansible is executed in check mode, return the diff between what's deployed and what will be deployed.
+- helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
+- helm_info - add hooks, notes and manifest as part of returned information (https://github.com/ansible-collections/kubernetes.core/pull/546).
+- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
+- helm_info - added possibility to get all values by adding get_all_values parameter (https://github.com/ansible-collections/kubernetes.core/pull/531).
+- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
+- helm_plugin - Add support for helm plugin update using state=update.
+- helm_repository - Ability to replace (overwrite) the repo if it already exists by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
+- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
+- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``, and ``ca_cert``.
+- helm_repository - mark `pass_credentials` as no_log=True to silence false warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
+- helm_template - add name (NAME of release) and disable_hook as optional module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+- helm_template - add support for -set-file, -set-json, -set and -set-string options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
+- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
+- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
+- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
+- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
+- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
+- k8s, k8s_scale, k8s_service - add support for resource definition as manifest via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
+- k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod (https://github.com/ansible-collections/kubernetes.core/issues/486).
+- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
+- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
+- k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
+- k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
+- k8s_log - add the ``all_containers`` for retrieving all containers' logs in the pod(s).
+- k8s_log - added the `previous` parameter for retrieving the previously terminated pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
+- k8s_log - added the `tail_lines` parameter to limit the number of lines to be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
+- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
+- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
+- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
+- kubectl.py - replace distutils.spawn.find_executable with shutil.which in the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
+
+Bugfixes
+--------
+
+- Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to the client, add conditional check on kubernetes client version as this feature is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
+- Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
+- Helm - Fix issue with alternative kubeconfig provided with validate_certs=False (https://github.com/ansible-collections/kubernetes.core/issues/538).
+- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
+- add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name (https://github.com/ansible-collections/kubernetes.core/issues/558).
+- common - Ensure the label_selectors parameter of _wait_for method is optional.
+- common - handle ``aliases`` passed from inventory and lookup plugins.
+- helm_template - evaluate release_values after values_files, insuring highest precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
+- import exception from ``kubernetes.client.rest``.
+- k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
+- k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
+- k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
+- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
+- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
+- k8s_info - fix issue when module returns successful true after the resource cache has been established during periods where communication to the api-server is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
+- k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
+- k8s_log - fix exception raised when the name is not provided for resources requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
+- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
+- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
+- module_utils/k8s/client.py - fix issue when trying to authenticate with host, client_cert and client_key parameters only.
+- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
+- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
+
+New Modules
+-----------
+
+- helm_pull - download a chart from a repository and (optionally) unpack it in local directory.
+
+v2.3.1
+======
+
+Bugfixes
+--------
+
+- Catch exception raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
+- Remove `omit` placeholder when defining resource using template parameter (https://github.com/ansible-collections/kubernetes.core/issues/431).
+- k8s - fix the issue when trying to delete resources using label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/433).
+- k8s_cp - fix issue when using parameter local_path with file on managed node. (https://github.com/ansible-collections/kubernetes.core/issues/421).
+- k8s_drain - fix error occurring when trying to drain node with disable_eviction set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
+
+v2.3.0
+======
+
+Minor Changes
+-------------
+
+- add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
+- fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
+- helm - add support for timeout cli parameter to allow setting Helm timeout independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
+- helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
+- helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
+- helm - when ansible is executed in check mode, return the diff between what's deployed and what will be deployed.
+- helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
+- helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
+- helm_plugin - Add support for helm plugin update using state=update.
+- helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
+- helm_repository - added support for ``host``, ``api_key``, ``validate_certs``, and ``ca_cert``.
+- helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+- k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
+- k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
+- k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
+- k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
+- k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
+- k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
+- k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
+- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
+- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
+- kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
+
+Bugfixes
+--------
+
+- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
+- common - Ensure the label_selectors parameter of _wait_for method is optional.
+- helm_template - evaluate release_values after values_files, insuring highest precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
+- import exception from ``kubernetes.client.rest``.
+- k8s_drain - fix error caused by accessing an undefined variable when pods have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
+- k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
+- k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
+- module_utils.common - change default opening mode to read-bytes to avoid bad interpretation of non ascii characters and strings, often present in 3rd party manifests.
+- remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
+- use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
+
+New Modules
+-----------
+
+- k8s_taint - Taint a node in a Kubernetes/OpenShift cluster
+
+v2.2.0
+======
+
+Minor Changes
+-------------
+
+- add support for in-memory kubeconfig in addition to file for k8s modules. (https://github.com/ansible-collections/kubernetes.core/pull/212).
+- helm - add support for history_max cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/164).
+- k8s - add support for label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/43).
+- k8s - add support for waiting on statefulsets (https://github.com/ansible-collections/kubernetes.core/pull/195).
+- k8s_log - Add since-seconds parameter to the k8s_log module (https://github.com/ansible-collections/kubernetes.core/pull/142).
+- new lookup plugin to support kubernetes kustomize feature. (https://github.com/ansible-collections/kubernetes.core/issues/39).
+- re-enable turbo mode for collection. The default is initially set to off (https://github.com/ansible-collections/kubernetes.core/pull/169).
+
+Bugfixes
+--------
+
+- common - import k8sdynamicclient directly to workaround Ansible upstream bug (https://github.com/ansible-collections/kubernetes.core/issues/162).
+- connection plugin - add arguments information into censored command (https://github.com/ansible-collections/kubernetes.core/pull/196).
+- fix resource cache not being used (https://github.com/ansible-collections/kubernetes.core/pull/228).
+- k8s - Fixes a bug where diff was always returned when using apply or modifying an existing object, even when diff=no was specified. The module no longer returns diff unless requested and will now honor diff=no (https://github.com/ansible-collections/kubernetes.core/pull/146).
+- k8s_cp - fix k8s_cp uploading when target container's WORKDIR is not '/' (https://github.com/ansible-collections/kubernetes.core/issues/222).
+- k8s_exec - add missing deprecation notice to return_code for k8s_exec (https://github.com/ansible-collections/kubernetes.core/pull/233).
+- k8s_exec - fix k8s_exec returning rc attribute, to follow ansible's common return values (https://github.com/ansible-collections/kubernetes.core/pull/230).
+- lookup - recommend query instead of lookup (https://github.com/ansible-collections/kubernetes.core/issues/147).
+- support the ``template`` param in all collections depending on kubernetes.core (https://github.com/ansible-collections/kubernetes.core/pull/154).
+
+New Plugins
+-----------
+
+Lookup
+~~~~~~
+
+- kustomize - Build a set of kubernetes resources using a 'kustomization.yaml' file.
+
+New Modules
+-----------
+
+- k8s_cp - Copy files and directories to and from pod.
+- k8s_drain - Drain, Cordon, or Uncordon node in k8s cluster
+
+v2.1.1
+======
+
+Bugfixes
+--------
+
+- check auth params for existence, not whether they are true (https://github.com/ansible-collections/kubernetes.core/pull/151).
+
+v2.1.0
+======
+
+Minor Changes
+-------------
+
+- remove cloud.common as default dependency (https://github.com/ansible-collections/kubernetes.core/pull/148).
+- temporarily disable turbo mode (https://github.com/ansible-collections/kubernetes.core/pull/149).
+
+v2.0.2
+======
+
+Bugfixes
+--------
+
+- Fix apply for k8s module when an array attribute from definition contains empty dict (https://github.com/ansible-collections/kubernetes.core/issues/113).
+- rename the apply function to fix broken imports in Ansible 2.9 (https://github.com/ansible-collections/kubernetes.core/pull/135).
+
+v2.0.1
+======
+
+Bugfixes
+--------
+
+- inventory - add community.kubernetes to list of plugin choices in k8s inventory (https://github.com/ansible-collections/kubernetes.core/pull/128).
+
+v2.0.0
+======
+
+Major Changes
+-------------
+
+- k8s - deprecate merge_type=json. The JSON patch functionality has never worked (https://github.com/ansible-collections/kubernetes.core/pull/99).
+- k8s_json_patch - split JSON patch functionality out into a separate module (https://github.com/ansible-collections/kubernetes.core/pull/99).
+- replaces the openshift client with the official kubernetes client (https://github.com/ansible-collections/kubernetes.core/issues/34).
+
+Minor Changes
+-------------
+
+- Add cache_file when DynamicClient is created (https://github.com/ansible-collections/kubernetes.core/pull/46).
+- Add configmap and secret hash functionality (https://github.com/ansible-collections/kubernetes.core/pull/48).
+- Add logic for cache file name generation (https://github.com/ansible-collections/kubernetes.core/pull/46).
+- Replicate apply method in the DynamicClient (https://github.com/ansible-collections/kubernetes.core/pull/45).
+- add ``proxy_headers`` option for authentication on k8s_xxx modules (https://github.com/ansible-collections/kubernetes.core/pull/58).
+- add support for using tags when running molecule test suite (https://github.com/ansible-collections/kubernetes.core/pull/62).
+- added documentation for ``kubernetes.core`` collection (https://github.com/ansible-collections/kubernetes.core/pull/50).
+- common - removed ``KubernetesAnsibleModule``, use ``K8sAnsibleMixin`` instead (https://github.com/ansible-collections/kubernetes.core/pull/70).
+- helm - add example for complex values in ``helm`` module (https://github.com/ansible-collections/kubernetes.core/issues/109).
+- k8s - Handle list of definition for option `template` (https://github.com/ansible-collections/kubernetes.core/pull/49).
+- k8s - `continue_on_error` option added (whether to continue on creation/deletion errors) (https://github.com/ansible-collections/kubernetes.core/pull/49).
+- k8s - support ``patched`` value for ``state`` option. patched state is an existing resource that has a given patch applied (https://github.com/ansible-collections/kubernetes.core/pull/90).
+- k8s - wait for all pods to update when rolling out daemonset changes (https://github.com/ansible-collections/kubernetes.core/pull/102).
+- k8s_scale - ability to scale multiple resource using ``label_selectors`` (https://github.com/ansible-collections/kubernetes.core/pull/114).
+- k8s_scale - new parameter to determine whether to continue or not on error when scaling multiple resources (https://github.com/ansible-collections/kubernetes.core/pull/114).
+- kubeconfig - update ``kubeconfig`` file location in the documentation (https://github.com/ansible-collections/kubernetes.core/issues/53).
+- remove old change log fragment files.
+- remove the deprecated ``KubernetesRawModule`` class (https://github.com/ansible-collections/community.kubernetes/issues/232).
+- replicate base resource for lists functionality (https://github.com/ansible-collections/kubernetes.core/pull/89).
+
+Breaking Changes / Porting Guide
+--------------------------------
+
+- Drop python 2 support (https://github.com/ansible-collections/kubernetes.core/pull/86).
+- helm_plugin - remove unused ``release_namespace`` parameter (https://github.com/ansible-collections/kubernetes.core/pull/85).
+- helm_plugin_info - remove unused ``release_namespace`` parameter (https://github.com/ansible-collections/kubernetes.core/pull/85).
+- k8s_cluster_info - returned apis as list to avoid being overwritten in case of multiple version (https://github.com/ansible-collections/kubernetes.core/pull/41).
+- k8s_facts - remove the deprecated alias from k8s_facts to k8s_info (https://github.com/ansible-collections/kubernetes.core/pull/125).
+
+Bugfixes
+--------
+
+- enable unit tests in CI (https://github.com/ansible-collections/community.kubernetes/pull/407).
+- helm - Accept ``validate_certs`` with a ``context`` (https://github.com/ansible-collections/kubernetes.core/pull/74).
+- helm - fix helm ignoring the kubeconfig context when passed through the ``context`` param or the ``K8S_AUTH_CONTEXT`` environment variable (https://github.com/ansible-collections/community.kubernetes/issues/385).
+- helm - handle multiline output of ``helm plugin list`` command (https://github.com/ansible-collections/community.kubernetes/issues/399).
+- k8s - fix merge_type option when set to json (https://github.com/ansible-collections/kubernetes.core/issues/54).
+- k8s - lookup should return list even if single item is found (https://github.com/ansible-collections/kubernetes.core/issues/9).
+- k8s inventory - remove extra trailing slashes from the hostname (https://github.com/ansible-collections/kubernetes.core/issues/52).
+
+New Modules
+-----------
+
+- k8s_json_patch - Apply JSON patch operations to existing objects
+
+v1.2.0
+======
+
+Minor Changes
+-------------
+
+- Adjust the documentation to clarify the fact ``wait_condition.status`` is a string.
+- Adjust the name of parameters of ``helm`` and ``helm_info`` to match the documentation. No playbook change required.
+- The Helm modules (``helm``, ``helm_info``, ``helm_plugin``, ``helm_plugin_info``, ``helm_plugin_repository``) accept the K8S environment variables like the other modules of the collections.
+- helm - add a ``skip_crds`` option to skip the installation of CRDs when installing or upgrading a chart (https://github.com/ansible-collections/community.kubernetes/issues/296).
+- helm - add optional support for helm diff (https://github.com/ansible-collections/community.kubernetes/issues/248).
+- helm_template - add helm_template module to support template functionality (https://github.com/ansible-collections/community.kubernetes/issues/367).
+- k8s - add a ``delete_options`` parameter to control garbage collection behavior when deleting a resource (https://github.com/ansible-collections/community.kubernetes/issues/253).
+- k8s - add an example for downloading manifest file and applying (https://github.com/ansible-collections/community.kubernetes/issues/352).
+- k8s - check if kubeconfig file is located on remote node or on Ansible Controller (https://github.com/ansible-collections/community.kubernetes/issues/307).
+- k8s - check if src file is located on remote node or on Ansible Controller (https://github.com/ansible-collections/community.kubernetes/issues/307).
+- k8s_exec - add a note about required permissions for the module (https://github.com/ansible-collections/community.kubernetes/issues/339).
+- k8s_info - add information about api_version while returning facts (https://github.com/ansible-collections/community.kubernetes/pull/308).
+- runtime.yml - update minimum Ansible version required for Kubernetes collection (https://github.com/ansible-collections/community.kubernetes/issues/314).
+
+Bugfixes
+--------
+
+- helm - ``release_values`` makes ansible always show changed state (https://github.com/ansible-collections/community.kubernetes/issues/274)
+- helm - make helm-diff plugin detection more reliable by splitting by any whitespace instead of explicit whitespace (``\s``) (https://github.com/ansible-collections/community.kubernetes/pull/362).
+- helm - return values in check mode when release is not present (https://github.com/ansible-collections/community.kubernetes/issues/280).
+- helm_plugin - make unused ``release_namespace`` parameter as optional (https://github.com/ansible-collections/community.kubernetes/issues/357).
+- helm_plugin_info - make unused ``release_namespace`` parameter as optional (https://github.com/ansible-collections/community.kubernetes/issues/357).
+- k8s - fix check_mode always showing changes when using stringData on Secrets (https://github.com/ansible-collections/community.kubernetes/issues/282).
+- k8s - handle ValueError when namespace is not provided (https://github.com/ansible-collections/community.kubernetes/pull/330).
+- respect the ``wait_timeout`` parameter in the ``k8s`` and ``k8s_info`` modules when a resource does not exist (https://github.com/ansible-collections/community.kubernetes/issues/344).
+
+v1.1.1
+======
+
+Bugfixes
+--------
+
+- k8s - Fix sanity test 'compile' failing because of positional args (https://github.com/ansible-collections/community.kubernetes/issues/260).
+
+v1.1.0
+======
+
+Major Changes
+-------------
+
+- k8s - Add support for template parameter (https://github.com/ansible-collections/community.kubernetes/pull/230).
+- k8s_* - Add support for vaulted kubeconfig and src (https://github.com/ansible-collections/community.kubernetes/pull/193).
+
+Minor Changes
+-------------
+
+- Add Makefile and downstream build script for kubernetes.core (https://github.com/ansible-collections/community.kubernetes/pull/197).
+- Add execution environment metadata (https://github.com/ansible-collections/community.kubernetes/pull/211).
+- Add probot stale bot configuration to autoclose issues (https://github.com/ansible-collections/community.kubernetes/pull/196).
+- Added a contribution guide (https://github.com/ansible-collections/community.kubernetes/pull/192).
+- Refactor module_utils (https://github.com/ansible-collections/community.kubernetes/pull/223).
+- Replace KubernetesAnsibleModule class with dummy class (https://github.com/ansible-collections/community.kubernetes/pull/227).
+- Replace KubernetesRawModule class with K8sAnsibleMixin (https://github.com/ansible-collections/community.kubernetes/pull/231).
+- common - Do not mark task as changed when diff is irrelevant (https://github.com/ansible-collections/community.kubernetes/pull/228).
+- helm - Add appVersion idempotence check to Helm (https://github.com/ansible-collections/community.kubernetes/pull/246).
+- helm - Return status in check mode (https://github.com/ansible-collections/community.kubernetes/pull/192).
+- helm - Support for single or multiple values files (https://github.com/ansible-collections/community.kubernetes/pull/93).
+- helm_* - Support vaulted kubeconfig (https://github.com/ansible-collections/community.kubernetes/pull/229).
+- k8s - SelfSubjectAccessReviews supported when 405 response received (https://github.com/ansible-collections/community.kubernetes/pull/237).
+- k8s - add testcase for adding multiple resources using template parameter (https://github.com/ansible-collections/community.kubernetes/issues/243).
+- k8s_info - Add support for wait (https://github.com/ansible-collections/community.kubernetes/pull/235).
+- k8s_info - update custom resource example (https://github.com/ansible-collections/community.kubernetes/issues/202).
+- kubectl plugin - correct console log (https://github.com/ansible-collections/community.kubernetes/issues/200).
+- raw - Handle exception raised by underlying APIs (https://github.com/ansible-collections/community.kubernetes/pull/180).
+
+Bugfixes
+--------
+
+- common - handle exception raised due to DynamicClient (https://github.com/ansible-collections/community.kubernetes/pull/224).
+- helm - add replace parameter (https://github.com/ansible-collections/community.kubernetes/issues/106).
+- k8s (inventory) - Set the connection plugin and transport separately (https://github.com/ansible-collections/community.kubernetes/pull/208).
+- k8s (inventory) - Specify FQCN for k8s inventory plugin to fix use with Ansible 2.9 (https://github.com/ansible-collections/community.kubernetes/pull/250).
+- k8s_info - add wait functionality (https://github.com/ansible-collections/community.kubernetes/issues/18).
+
+v1.0.0
+======
+
+Major Changes
+-------------
+
+- helm_plugin - new module to manage Helm plugins (https://github.com/ansible-collections/community.kubernetes/pull/154).
+- helm_plugin_info - new modules to gather information about Helm plugins (https://github.com/ansible-collections/community.kubernetes/pull/154).
+- k8s_exec - Return rc for the command executed (https://github.com/ansible-collections/community.kubernetes/pull/158).
+
+Minor Changes
+-------------
+
+- Ensure check mode results are as expected (https://github.com/ansible-collections/community.kubernetes/pull/155).
+- Update base branch to 'main' (https://github.com/ansible-collections/community.kubernetes/issues/148).
+- helm - Add support for K8S_AUTH_CONTEXT, K8S_AUTH_KUBECONFIG env (https://github.com/ansible-collections/community.kubernetes/pull/141).
+- helm - Allow creating namespaces with Helm (https://github.com/ansible-collections/community.kubernetes/pull/157).
+- helm - add aliases context for kube_context (https://github.com/ansible-collections/community.kubernetes/pull/152).
+- helm - add support for K8S_AUTH_KUBECONFIG and K8S_AUTH_CONTEXT environment variable (https://github.com/ansible-collections/community.kubernetes/issues/140).
+- helm_info - add aliases context for kube_context (https://github.com/ansible-collections/community.kubernetes/pull/152).
+- helm_info - add support for K8S_AUTH_KUBECONFIG and K8S_AUTH_CONTEXT environment variable (https://github.com/ansible-collections/community.kubernetes/issues/140).
+- k8s_exec - return RC for the command executed (https://github.com/ansible-collections/community.kubernetes/issues/122).
+- k8s_info - Update example using vars (https://github.com/ansible-collections/community.kubernetes/pull/156).
+
+Security Fixes
+--------------
+
+- kubectl - connection plugin now redact kubectl_token and kubectl_password in console log (https://github.com/ansible-collections/community.kubernetes/issues/65).
+- kubectl - redacted token and password from console log (https://github.com/ansible-collections/community.kubernetes/pull/159).
+
+Bugfixes
+--------
+
+- Test against stable ansible branch so molecule tests work (https://github.com/ansible-collections/community.kubernetes/pull/168).
+- Update openshift requirements in k8s module doc (https://github.com/ansible-collections/community.kubernetes/pull/153).
+
+New Modules
+-----------
+
+- helm_plugin - Manage Helm plugins
+- helm_plugin_info - Gather information about Helm plugins
+
+v0.11.1
+=======
+
+Major Changes
+-------------
+
+- Add changelog and fragments and document changelog process (https://github.com/ansible-collections/community.kubernetes/pull/131).
+
+Minor Changes
+-------------
+
+- Add action groups for playbooks with module_defaults (https://github.com/ansible-collections/community.kubernetes/pull/107).
+- Add requires_ansible version constraints to runtime.yml (https://github.com/ansible-collections/community.kubernetes/pull/126).
+- Add sanity test ignore file for Ansible 2.11 (https://github.com/ansible-collections/community.kubernetes/pull/130).
+- Add test for openshift apply bug (https://github.com/ansible-collections/community.kubernetes/pull/94).
+- Add version_added to each new collection module (https://github.com/ansible-collections/community.kubernetes/pull/98).
+- Check Python code using flake8 (https://github.com/ansible-collections/community.kubernetes/pull/123).
+- Don't require project coverage check on PRs (https://github.com/ansible-collections/community.kubernetes/pull/102).
+- Improve k8s Deployment and Daemonset wait conditions (https://github.com/ansible-collections/community.kubernetes/pull/35).
+- Minor documentation fixes and use of FQCN in some examples (https://github.com/ansible-collections/community.kubernetes/pull/114).
+- Remove action_groups_redirection entry from meta/runtime.yml (https://github.com/ansible-collections/community.kubernetes/pull/127).
+- Remove deprecated ANSIBLE_METADATA field (https://github.com/ansible-collections/community.kubernetes/pull/95).
+- Use FQCN in module docs and plugin examples (https://github.com/ansible-collections/community.kubernetes/pull/146).
+- Use improved kubernetes diffs where possible (https://github.com/ansible-collections/community.kubernetes/pull/105).
+- helm - add 'atomic' option (https://github.com/ansible-collections/community.kubernetes/pull/115).
+- helm - minor code refactoring (https://github.com/ansible-collections/community.kubernetes/pull/110).
+- helm_info and helm_repository - minor code refactor (https://github.com/ansible-collections/community.kubernetes/pull/117).
+- k8s - Handle set object retrieved from lookup plugin (https://github.com/ansible-collections/community.kubernetes/pull/118).
+
+Bugfixes
+--------
+
+- Fix suboption docs structure for inventory plugins (https://github.com/ansible-collections/community.kubernetes/pull/103).
+- Handle invalid kubeconfig parsing error (https://github.com/ansible-collections/community.kubernetes/pull/119).
+- Make sure Service changes run correctly in check_mode (https://github.com/ansible-collections/community.kubernetes/pull/84).
+- k8s_info - remove unneccessary k8s_facts deprecation notice (https://github.com/ansible-collections/community.kubernetes/pull/97).
+- k8s_scale - Fix scale wait and add tests (https://github.com/ansible-collections/community.kubernetes/pull/100).
+- raw - handle condition when definition is none (https://github.com/ansible-collections/community.kubernetes/pull/139).
+
+v0.11.0
+=======
+
+Major Changes
+-------------
+
+- helm - New module for managing Helm charts (https://github.com/ansible-collections/community.kubernetes/pull/61).
+- helm_info - New module for retrieving Helm chart information (https://github.com/ansible-collections/community.kubernetes/pull/61).
+- helm_repository - New module for managing Helm repositories (https://github.com/ansible-collections/community.kubernetes/pull/61).
+
+Minor Changes
+-------------
+
+- Rename repository to ``community.kubernetes`` (https://github.com/ansible-collections/community.kubernetes/pull/81).
+
+Bugfixes
+--------
+
+- Make sure extra files are not included in built collection (https://github.com/ansible-collections/community.kubernetes/pull/85).
+- Update GitHub Actions workflow for better CI stability (https://github.com/ansible-collections/community.kubernetes/pull/78).
+- k8s_log - Module no longer attempts to parse log as JSON (https://github.com/ansible-collections/community.kubernetes/pull/69).
+
+New Modules
+-----------
+
+- helm - Manages Kubernetes packages with the Helm package manager
+- helm_info - Get information from Helm package deployed inside the cluster
+- helm_repository - Add and remove Helm repository
+
+v0.10.0
+=======
+
+Major Changes
+-------------
+
+- k8s_exec - New module for executing commands on pods via Kubernetes API (https://github.com/ansible-collections/community.kubernetes/pull/14).
+- k8s_log - New module for retrieving pod logs (https://github.com/ansible-collections/community.kubernetes/pull/16).
+
+Minor Changes
+-------------
+
+- k8s - Added ``persist_config`` option for persisting refreshed tokens (https://github.com/ansible-collections/community.kubernetes/issues/49).
+
+Security Fixes
+--------------
+
+- kubectl - Warn about information disclosure when using options like ``kubectl_password``, ``kubectl_extra_args``, and ``kubectl_token`` to pass data through to the command line using the ``kubectl`` connection plugin (https://github.com/ansible-collections/community.kubernetes/pull/51).
+
+Bugfixes
+--------
+
+- k8s - Add exception handling when retrieving k8s client (https://github.com/ansible-collections/community.kubernetes/pull/54).
+- k8s - Fix argspec for 'elements' (https://github.com/ansible-collections/community.kubernetes/issues/13).
+- k8s - Use ``from_yaml`` filter with lookup examples in ``k8s`` module documentation examples (https://github.com/ansible-collections/community.kubernetes/pull/56).
+- k8s_service - Fix argspec (https://github.com/ansible-collections/community.kubernetes/issues/33).
+- kubectl - Fix documentation in kubectl connection plugin (https://github.com/ansible-collections/community.kubernetes/pull/52).
+
+New Modules
+-----------
+
+- k8s_exec - Execute command in Pod
+- k8s_log - Fetch logs from Kubernetes resources
+
+v0.9.0
+======
+
+Major Changes
+-------------
+
+- k8s - Inventory source migrated from Ansible 2.9 to Kubernetes collection.
+- k8s - Lookup plugin migrated from Ansible 2.9 to Kubernetes collection.
+- k8s - Module migrated from Ansible 2.9 to Kubernetes collection.
+- k8s_auth - Module migrated from Ansible 2.9 to Kubernetes collection.
+- k8s_config_resource_name - Filter plugin migrated from Ansible 2.9 to Kubernetes collection.
+- k8s_info - Module migrated from Ansible 2.9 to Kubernetes collection.
+- k8s_scale - Module migrated from Ansible 2.9 to Kubernetes collection.
+- k8s_service - Module migrated from Ansible 2.9 to Kubernetes collection.
+- kubectl - Connection plugin migrated from Ansible 2.9 to Kubernetes collection.
+- openshift - Inventory source migrated from Ansible 2.9 to Kubernetes collection.
diff --git a/ansible_collections/kubernetes/core/CONTRIBUTING.md b/ansible_collections/kubernetes/core/CONTRIBUTING.md
new file mode 100644
index 00000000..431d8238
--- /dev/null
+++ b/ansible_collections/kubernetes/core/CONTRIBUTING.md
@@ -0,0 +1,80 @@
+# Contributing
+
+## Getting Started
+
+General information about setting up your Python environment, testing modules,
+Ansible coding styles, and more can be found in the [Ansible Community Guide](
+https://docs.ansible.com/ansible/latest/community/index.html).
+
+
+## Kubernetes Collections
+
+### kubernetes.core
+
+This collection contains modules and plugins contributed and maintained by the Ansible Kubernetes
+community.
+
+New modules and plugins developed by the community should be proposed to `kubernetes.core`.
+
+## Submitting Issues
+All software has bugs, and the `kubernetes.core` collection is no exception. When you find a bug,
+you can help tremendously by [telling us about it](https://github.com/ansible-collections/kubernetes.core/issues/new/choose).
+
+If you should discover that the bug you're trying to file already exists in an issue,
+you can help by verifying the behavior of the reported bug with a comment in that
+issue, or by reporting any additional information.
+
+## Pull Requests
+
+All modules MUST have integration tests for new features.
+Bug fixes for modules that currently have integration tests SHOULD have tests added.
+New modules should be submitted to the [kubernetes.core](https://github.com/ansible-collections/kubernetes.core) collection and MUST have integration tests.
+
+Expected test criteria:
+* Resource creation under check mode
+* Resource creation
+* Resource creation again (idempotency) under check mode
+* Resource creation again (idempotency)
+* Resource modification under check mode
+* Resource modification
+* Resource modification again (idempotency) under check mode
+* Resource modification again (idempotency)
+* Resource deletion under check mode
+* Resource deletion
+* Resource deletion (of a non-existent resource) under check mode
+* Resource deletion (of a non-existent resource)
+
+Where modules have multiple parameters we recommend running through the 4-step modification cycle for each parameter the module accepts, as well as a modification cycle where as most, if not all, parameters are modified at the same time.
+
+For general information on running the integration tests see the
+[Integration Tests page of the Module Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/testing_integration.html#testing-integration),
+especially the section on configuration for cloud tests. For questions about writing tests the Ansible Kubernetes community can be found on Libera.Chat IRC as detailed below.
+
+### Updating documentation
+
+Modules and plugins documentation is autogenerated using ``collection_prep_add_docs`` command from [collection_prep](https://github.com/ansible-network/collection_prep) package.
+
+You can install ``collection_prep`` using
+
+ # git clone https://github.com/ansible-network/collection_prep
+ # cd collection_prep
+ # pip install .
+
+After installation, you can update documentation
+
+ # collection_prep_add_docs -p /<PATH_OF_COLLECTION>/kubernetes/core
+
+Review the changes and create a pull request using updated files.
+
+### Code of Conduct
+The `kubernetes.core` collection follows the Ansible project's
+[Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html).
+Please read and familiarize yourself with this document.
+
+### IRC
+Our IRC channels may require you to register your nickname. If you receive an error when you connect, see
+[Libera.Chat's Nickname Registration guide](https://libera.chat/guides/registration) for instructions.
+
+The `#ansible-kubernetes` channel on [libera.chat](https://libera.chat/) IRC is the main and official place to discuss use and development of the `kubernetes.core` collection.
+
+For more information about Ansible's Kubernetes integration, browse the resources in the [Kubernetes Working Group](https://github.com/ansible/community/wiki/Kubernetes) Community wiki page.
diff --git a/ansible_collections/kubernetes/core/FILES.json b/ansible_collections/kubernetes/core/FILES.json
new file mode 100644
index 00000000..edb69b20
--- /dev/null
+++ b/ansible_collections/kubernetes/core/FILES.json
@@ -0,0 +1,4191 @@
+{
+ "files": [
+ {
+ "name": ".",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/patchback.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ed69f87ea46171cb574fb77dc74fdbd7a269d4cad8d5ba6494d64d99842ef8e4",
+ "format": 1
+ },
+ {
+ "name": ".github/stale.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "923b49f6fb8b325ea890d05a42537b3f9c5aaf26b64a704c0fef4b696aa6a4bb",
+ "format": 1
+ },
+ {
+ "name": "changelogs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/changelog.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e116a242c550ab4f2dde8c5d0db5f75085b34a39160cfa6c3ffd180d21b4c07e",
+ "format": 1
+ },
+ {
+ "name": "changelogs/config.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2903835efadf1f03f8a05ba8428d1530259e322d039dcd3edbe707bcaea82e3d",
+ "format": 1
+ },
+ {
+ "name": "docs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "docs/docsite",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/kubernetes_scenarios",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7ce06411318e79c00c9c46d0faf31f34de731bff5ef22e00b57c6e8f26c2dac8",
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "29e7c5d13c2ea6ed35247acf7a05a1008662f950ff79c8bc2a9b41887d004975",
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b98746ba71dce9ba385228e55c7c367888a3fc569bfa37261475bc24179406a8",
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b7a4b5b5c86e756523c86c7aba91b768872f3b3af9fb5b0bc37aae6139dfd991",
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/rst/scenario_guide.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d14c604f7f6b2311f9b9688dcea5d18357920d7ccd228539b2cf0e52a997c822",
+ "format": 1
+ },
+ {
+ "name": "docs/docsite/extra-docs.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "79c40762026c71888c65490babe92e16d252af7113dec735cf75630af8245d47",
+ "format": 1
+ },
+ {
+ "name": "docs/ansible_turbo_mode.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f2b28caa1289a1267341d148134c9fc6544a05eb5146d80a51da6703d995cecb",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_info_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "89adae8f40c2da10c350d9cfc1b3844104f9c8d51da2fbf13b9301cad91b5a96",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a8e5c2ca7aa70bca9641dd290814de18d2ade93939cdbae64a7b7484a5418565",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_plugin_info_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "51ba9800d8f92207fb2580158d75a05c942882fe237dfdbceb1b16014e13ef5d",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_plugin_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b9ed32e51ca564332c6c070793ed4950b9c52394f62f37416658988d5d16efd0",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_pull_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ca57afa3fe3b2c69f44bc948c2088d7056d197b65281107bc9ce32d31c1b3adf",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_repository_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4c7b2efcf6832cf1b53e9fec808449ae5ea973306ae2a3eeeed4ea7405327e06",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.helm_template_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70f91cecfcc664c8ac35b58ce351fbf2a8f3051608580b180c830ca617b4f856",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_cluster_info_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "061da0cf6ce62e3ba49491f11533d76e4dc8073ca8a9dbee2d3c981ad56f3d70",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_cp_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3cb1cc05abdeaa923c87b17cff8b7c6d2aab7273b67ef6879f594cfc5ec1489d",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_drain_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b8e412c8036aa76e0ae785941b8bf317ab71296d910da6750b3793271607533c",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_exec_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b0c29c1755d04f7be9391da36a1803b6040f18fe7efdd72a14051ad758ed9e5",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_info_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d26adc890f69739b4744ad88a1757cbf68fca96cf9b1394a7614ffc6760732b8",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_inventory.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f0e7072fe1ebf5e12870ba93e0de9bb571f3538322d60ee6925ac025023b4ff4",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_json_patch_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b628ee67825e39bf7517eae54e7c322968719807cf349ed650674c99c334607a",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_log_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "407fd3cf0c037d5a5226c554f5d318e549b9e9fb862b9835d958612a0f515f27",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_lookup.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fac344797caf1dcdf99adeec0a0d850d1fd74fbf46f3a226b52931f1fa6bca0d",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "890765b7b747dc2762b0683c704fec4d18ddac7daccb68e28a75cf9f79a47893",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_rollback_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9fd9867dcd6be0868d7fb419b9ce930f6ecbbbf66af9e4ec22ba3c3ce8151d5c",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_scale_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "89d2cb58fcea0081652fd6a72f2370900cd9cd54b4444cc6a1cced61d505127e",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_service_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a766b619b69267f29cab51baf780de200fa7fa2377fcda92585657e1dc849d0f",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.k8s_taint_module.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d64a4250bdca5ac386fa4539fa837c7d89a26e63d24fc01777d1f2125b70f160",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.kubectl_connection.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7cb93572ffd283a453b53bd8d100ce0949a6a6cdce850c5deecd22cafa0eb4e8",
+ "format": 1
+ },
+ {
+ "name": "docs/kubernetes.core.kustomize_lookup.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b6006ecdeaec488a59651653d7c48c0f4d156f49fc3a12409f75a187eafb57b7",
+ "format": 1
+ },
+ {
+ "name": "meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "meta/runtime.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "234b418a0a39617e8ab74b92c221dddf0b8705b36fc755c0ba70d8e070809365",
+ "format": 1
+ },
+ {
+ "name": "plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/action",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/action/helm.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/helm_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/helm_plugin.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/helm_plugin_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/helm_repository.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_cluster_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_cp.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_drain.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_exec.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_log.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_rollback.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_scale.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_service.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/ks8_json_patch.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/action/k8s_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c8c0b75ee4f8568f7eb0aa93fbf0b1daeb8eb1ffe9d92e8cfc76a4e5d47cc20",
+ "format": 1
+ },
+ {
+ "name": "plugins/connection",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/connection/kubectl.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5895e521f261d2172764464c56e886ce61652bb8f9d0b88b51afb909da5d159f",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/helm_common_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a66b232cb2fa3c8a28498bd30389ca8f4ffd7ae97b70bfe601788d46779f9cb3",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_auth_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8d189a6ab2d0937cd572d2510823ecbe1a9742d5bed23a6406eae926aa1f7f7d",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_delete_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3e47df1b0ac656ad1b756b0628aeef1bbd47baa2dfbff643fd151467d0d18c97",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_name_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "293ef90ac4a1ad52677f22c747dda0d9e740c1a8e8c1fc783d110c2bbb35dc76",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_resource_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "30875d624f093085908955fb0ff53ab73602e5dffa66cbcba32de0a3e48785b3",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_scale_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "81ae1fe5c034a916cba915be2a35406d56340ead6a3189963911ebc93796d322",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_state_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "569e138705ec9b5bf2ba429cc0e71e87e4459cd1289470fb71e7436cd949f1c3",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/k8s_wait_options.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3c86e0272f62fbfc49c1c5eeeb4d7c61d9726bcec5e8965ae5c77edcab97296",
+ "format": 1
+ },
+ {
+ "name": "plugins/filter",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/filter/k8s.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1732300a6c48e6e6e678ea2b1dffb4a22779c6c7fe04f9fe64fd397df08af7c1",
+ "format": 1
+ },
+ {
+ "name": "plugins/filter/k8s_config_resource_name.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "47eb4fec78642ef7f76fe378beeeed7a2bb2bb344838adcc1ac6f178578ee7ee",
+ "format": 1
+ },
+ {
+ "name": "plugins/inventory",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/inventory/k8s.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "246cedb5fb08aa567cb266fcf9ac6513dffaa68ea75e33e8f2fa7e23cb12f9d5",
+ "format": 1
+ },
+ {
+ "name": "plugins/lookup",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/lookup/k8s.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2b24cf87c1463e091f41879aa2117b532dc481b74f7e9999cd049cdb5a8ee1ac",
+ "format": 1
+ },
+ {
+ "name": "plugins/lookup/kustomize.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cf1be05e740300ccb2d9a64f89024f8e645e9a3f33d921abd4c9e7e139645785",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/client",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/client/discovery.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9e91dfdcba133e090d1cba79f827ee09ed1a3915eee3e08b526e79ccdcac37c1",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/client/resource.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5ecbd5410095b770939270966962c5e939b7e662eb6f51c1271d294e6e683ee2",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/client.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f7428c830e53238c9682715651d3a125d172bea223970d851cc823291bb84c17",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/core.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "55a0a8cceda4e9ca604fe724af5d6cf82d9e382d138b110ac5d7ba9b073549d5",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/exceptions.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "68fc2551bb7c65b1d0352c6761d2f75a6ca59fa773f08faa3356ab4787c040eb",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/resource.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bf5d3132b51a4b725c11cd9a69bc75274663059234b56a6231271bf8c23bcaef",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/runner.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d1a9e22e30f2a1faf0d84687d5181517fda5844c97003e0d24e4b024d7412dd",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/service.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f3c774651fac14bfd191bff16e5c374049dd434880d63ca9a7a1b4dbde7bd7b",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8s/waiter.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c5c11459d7e02516b111cb64a5e31cf3afa1d9a9ffadfbc87ab477d5684d49d7",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/_version.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da42772669215aa2e1592bfcba0b4cef17d06cdbcdcfeb0ae05e431252fc5a16",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/ansiblemodule.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e6a42da6322ac61fc8c0d86a9b60c4ad4255a2ba19d0c69a139c80ace2773e4f",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/apply.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d13b411d95075d9aca971d7c4badd496a981011903d08f93ae0cd62f73968d10",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/args_common.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fdb406f9194a6a3cd97d6d805fb02ffd9d51e4209d7bac04c2c7c38ebe99dff2",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/common.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1ba4d32d44ef9366cb5f181a91692f269aac5f4ccc4c971237ac720a1c47cbe3",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/copy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e225cb19d811f0eb677f624db73f65ff5ead23e3701b7a266a9d5d03a3e99151",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/exceptions.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4256700ac9b1b0b29a0daa8d24da068a8435413cdd927b9613f4fa568e5ee450",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/hashes.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8495aceb22009f8529c1f5a82f4c338d9a727d23d0a85f7386183609c9e34c5c",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/helm.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "03c4cccb138537a8291f32025575c7ccc1a6b903b84a9f492851a1e2d07c2a07",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/helm_args_common.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e062ea6af707d2e71a4bf17c5fdc847bbb7867c53c4d6233ff8644991a136a8",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/k8sdynamicclient.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c58236ebcf4a9d39ccce1fb9f0084c716596fa269557553db1d421f9769c1088",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a912a35f5014bdf2a30625614b05acd2d71fabaea2b980bb4f6b110151e3e345",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/version.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c009a2e470b5c1e2cfc73efb061b3289f3da5064c85ad31dd664433ddb7b97b7",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1213cd7c02f4e2f28f4126c74e3145dfbd4b04185fb74c3a3b930f49ffbaaac1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5aa8e19162522752cd1625cd2953a2d4dfcad29dfb08607d28a62e0929aec8f8",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_plugin.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0425eaf1efe23f6d9eb6812300d0525eee18c769e80c6c74e193ee376c05901b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_plugin_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "80794a520c59990974d93c32d813ecee8de50b2cb36ababde183ee5db62c8cfa",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_pull.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da8a930e40e61287c5da5a1e4c15c29907d8330d060b89ecdc3a238855553c03",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_repository.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2d348193a38287251a86a8efb47a52d26fdae877447dc9933a1ee14281e9d3ee",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/helm_template.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c9b5f60a07127da4cdf139bbd83fe728aa66320ae644a1906a39fb87a871502d",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "efeb2745cafcf037d68b0a8b7594cd30e2743bddcaed073a864854ede685e162",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_cluster_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a04f656c51d094f1acb7b347d39da19ab3c46b35de9f07f23b9910ae601665f7",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_cp.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f5cd6f3791bfb5856900f4d3c24bb7dc4c2f895e89c619a0e1480b3634c75330",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_drain.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c5aa3613e22210c8800b69cdf2dd6847d743efa9fde99a13df60f86a6c2068a1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_exec.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3e85e93a987900104d20a7c8ed736df1a720c072dd181d4a092fb5344e08257a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f1c5cefb218b7fc7887213a8905385a9627c02fd228b91238c72061ea6da0cf3",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_json_patch.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "44097a47c792a7899ff04788d0f958a1819194224a5588eb5a5db5f448722be7",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_log.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4907cd48fae87e481961f29edea0dd460071d73513c3d5bae85dc63d882f4b5e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_rollback.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ee962569276d6117500f57ef2bac32e3dc75940855e2f351738318e6700ebd3a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_scale.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5486c6a62a1d27585485bef37fb5abde8a477543f7a37c6e42e5d4d65bcd8730",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_service.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da88e5c089db282cf1413ff002a6e781c7f05a54689c992494b52b9054689a41",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/k8s_taint.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ec21f39fd4efd743655d36a8091c24b7bfcd76af239cb5cb068415fad9552517",
+ "format": 1
+ },
+ {
+ "name": "tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4f8b1fa71279ad05526d564d79582b3be0ea406397bdde007f7f84cf557fc063",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart-v2",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart-v2/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9979f18c89ec57fb48ad851cd33e760f0ff1807a47cd4999fc2d562e155fd098",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart-v2/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c09667591633d997f2a6ef625d6aec9b0854b6fa7ec51bbee42b8ac460d6f3c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "18f299d28d316f4f389d902630342476cad9fa39e71b118f9653c2b768671a45",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/appversionless-chart/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fa6d86a1333abbc0bce9320952b49ceeda05fed029ab498c2bc9633b0507d62a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/dep-up",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/dep-up/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b1942642cdfcb72cf9130dcfa4c7db1dab07ca1a2f3bc4eada8dcc6e647a9e3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/dep-up/values.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e7e06f6160ada3c85ffe940ad3045be134a2630db4ef337c94136c326acbb795",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart-v2",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart-v2/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9979f18c89ec57fb48ad851cd33e760f0ff1807a47cd4999fc2d562e155fd098",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart-v2/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cc34a7fb5f306592164960bda0f3be7160a0a95e0680c26e094e68f9d7b17e61",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart/templates/configmap.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "18f299d28d316f4f389d902630342476cad9fa39e71b118f9653c2b768671a45",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-chart/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b1d6b7aaa7b3de76b01f935c002b06f7897477042f872aa732d9847894a2e6af",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-crds",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-crds/crds",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-crds/crds/crd.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e5b364fef4a2265bb39e4cf411bf28e3da6df6c4990c0d18a8b58d253141fd17",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/test-crds/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a3098e5531bbeea8677e05205809430b1b65749ef96ffc61c0bbc2ccd534191e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/files/values.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dcaf3f49ec12d4365ac0b9dba73255eeba9fea38895c7e63dc6e1ea0fe1fdb02",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/library",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/library/helm_test_version.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fb24cdcfd0d0e43450817fb51e96c4e78cc9ed74a4d65493a5c31e71398e2fee",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "16bb2a70e1f18f3ee0e6e4f9738a0533f2286c3163f9ba487a2af7c1d77675da",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/tests_chart",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/tests_chart/from_local_path.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "11d85f5e3f5b0a9d7e487a5907d7c4abfdb9c309146b811336aed11bf87fff17",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/tests_chart/from_repository.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0ac2c2a2df4f9f4ea023bf2bd1655e127282963bd0db0eafe06ced5eef836404",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/tests_chart/from_url.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3a0e7c533c4fd8a0f3876498fcf4cbc43398da6f05fc9559a20f2d0a8822dc7a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/install.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ade2b4fda2a79580f34163a9c7df12c017d8e38285342c9f281995f3179d477f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b0efdc4f0f86755e2f8675141088de4ebbbcc8d7c035248212b20b40a47e0cfb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/run_test.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "951714c0c82ed7edc2c3217592668cd6d0e89dca1b04347d6bd5261ba5475412",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/test_crds.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3b9e126f8f223d41dd43a7848ed06fafd15b8f5fd0bee7eea896d84a3cdd4e7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/test_helm_not_installed.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ad5395bdeccad7828b703759b23d4d689a7e4a926eacf4a30af7caafcad2ace",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/test_helm_uninstall.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b7b2a8d5ec8bda414225cadc593308480165473842b3d6b7eacbbc77bd68d64",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/test_read_envvars.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "43efeb25d57f49283c97e1585d5d05e5d8e52b3a44eae7292aa5c8f11bf04cd3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/test_up_dep.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2874f5c6212125dfd782d4dd03e39eac0a1f874ec26c28dd0d52b82fcc5f8cd0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/tasks/tests_chart.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "49ac759a792169559880988995dead3d5ce25540108e2c9f96fe63aeea26e318",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a2dfeff186fbdc681ec850d230d827f6c2c874d598e5181313cfcf6800832100",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e161fd07123767930d87054ce50d82038fd5c25d6b093afc1feb61fb5e6d46d5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/files/test-chart",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/files/test-chart/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "18f299d28d316f4f389d902630342476cad9fa39e71b118f9653c2b768671a45",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/files/test-chart/Chart.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b1d6b7aaa7b3de76b01f935c002b06f7897477042f872aa732d9847894a2e6af",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c62410da25d2dbb4bc0c82bd69412598750a7b3f12e1dfdf357e750cd7efb78e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e369b170ddb55919f665ffb224c8cbef7df5713b8961a9b016524cc0f8b9b43f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_diff/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0fe75a38d2132b50d7539eb71391cf8b0b70203d0e311c5c5be829fd0deccc02",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b21a19b5f11aca3bfe7d4849b23550f98cb9a38b305e75804c42f92fe9e385ff",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "706a0802afe247b21d705adcf9438b038b0d27a20202880fd729aaa46ee4e419",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks/from_in_memory_kubeconfig.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "80c5cb8e54afcfe56edcb20af674842a69bb9de52e06329fdc1a63785799c16d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_cacert.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a359babf91296ee84bf38cff248124abc310a72f8f6ad2e0f2feb5cfebde278f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_validate_certs.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "df195f27dc10ab15f13e55c672ab0d7093ead0cbab05e265cd61cea1179a1f33",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fed1323a4ae00ebe0c13dda54739ccf4d2188a901d5a27080594e9c54d16d498",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/tasks/tests_helm_auth.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d5cdbfb44509f3e21acd4ee351e1d28c9564b8b53369de25d4ce9c5316e88f8a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_kubeconfig/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6881d2c3d8e4323d9010284025fe44d0512f308fbf95057b4f25f7a7c6ab4cee",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "54fe0fec62aa8599fd6ff15e47ae9909281f62bd0d1b7a2c2ef4583fecf7f5d8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/files/sample_plugin",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/files/sample_plugin/plugin.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1a0e33cd8669e3052458cb1a25a1af6bd9ef76e58eca5e3720637e8ab516111d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9bba6e26661bb086d7e9db12fe0b2343f1cd83fda97bc6275c6d226711008732",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "22ca81c5a2d1a08b046f70ec3266e2b18c51a0bb10957b354fbf840bf79d1ca8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_plugin/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f9dd7470d4b1bf31b0664a480955a121f2f83105f78f820e649cf6f6822fde5d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_pull",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_pull/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_pull/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d6b0ed170fb58beac78bbbda4ef240a1dad8291b3f4eae68cfa44535527fbc23",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_pull/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9324d1375534c1b2cd31e7d392548e1d7fb8c4b96b8bda1a5fd8d2e6d03521e5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f97c6217ef12599cfcbb953747ac534cb9967e8530c268e6ed56a0bf51e9613e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9bba6e26661bb086d7e9db12fe0b2343f1cd83fda97bc6275c6d226711008732",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5a261c0f6b12f0bf72deb2f49a576cbefaf9570db7e3fbd9251b32768527f2b2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_repository/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6bdfe5ff6ae8ca3ee241def5499a8b7fbe977142e7381934c73b709784123607",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7bb5d1f3124e2053aeb54c5c40cb54587386a3f0a338f28f80a039bef6c7bd6b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "706a0802afe247b21d705adcf9438b038b0d27a20202880fd729aaa46ee4e419",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "874a80d269db0f7c6297b0eb0749dd569599562f4996aab79c40e5449a9dd19e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/helm_set_values/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1da51e4624ae654400e248308cc2a3c2462be7caefa4420859e2a210146daf78",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b4022584c6c1f438eed98fb8ccf09315b6a42a2c2ef6410c8215dc6fffddd778",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9c3787ff718f388c613ef21205c144de2b7bb193d5acefcb56c67dd9fed0a006",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/install_helm/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5ea375becd3088862c16fc97fe379532c583079829fcf1fdcb549e6808262fb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/vars",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/vars/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b7e8c47b08aa790e2ac087169e6bbefbbeecb65892e50165250be616b78155e3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/create_resources.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6167d41c39b455dd38a1adbef6db6ef0a2462e31d904d0b56f4a1f5e7435a81a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/delete_resources.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fbcdbc2bb7c3460d47a5bb9951686d97ed9be97050689dfc1a03581ded02abe0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/play.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f211af65f7508b30d9d938ceae299bbe40eebdd2f820393b9b10f4c5c94bf3f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/playbooks/test.inventory_k8s.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6437d90b9a8f0564bf714d0b4b226fa3a5ba44d057d8f1a693c52624f413f92a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "27896717f6976c7ba215dd050a7699ab34edf3ce1921ec28db8f239a915161e6",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/inventory_k8s/runme.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5415aa52dcafe384fbd790cc1af78830c102d68eb355b5cbf757fa0c86a953e7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_access_review",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_access_review/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_access_review/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aa73c4a87e4a54f6f58904b793357b19e297a1db6625e6a49e5ed24181cde560",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_access_review/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "af13a34bbd3fd265aaed6d88eff8518286852e423510ffab27aa1a3c1863469d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6d53b22bd468c9961454e2a829e3904d3245df0385a92b98ed315d02496d2028",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eed0f46a8c4d27944be98eb6ce1d479c161a7e1351fdd874ab355e2d765a3947",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8f755c3bc2a38414b1460e2c8d2578f97b53891177d6116181fcd4d8921daad4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_append_hash/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e41a6e3c02288e649ff00036d6a53272b3ea77651ac003820851aeb6b6a6c14d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bbb53dbe8d4d2dfa74e23922e85c84bc0d3976328257b389736c393169bad90b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eed0f46a8c4d27944be98eb6ce1d479c161a7e1351fdd874ab355e2d765a3947",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b36ee1d085cbbe29e2aa89a824c4fff99782b24044ad3da3e3d200bbdee2c49b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/tasks/server_side_apply.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "03fb6ca0b70f768f9053e064d1c71fdb6df2bcf951e5452b2fded4b8026e2123",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_apply/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f23f1e29aff150d3bbceb2294560871dd83c10ca64ce477f778887f3f40bf86",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a455afb6bd96cbde82fb079aa5da2ee22f6438f477c980c4da9c38017e646c85",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "706a0802afe247b21d705adcf9438b038b0d27a20202880fd729aaa46ee4e419",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/tasks/check_mode.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c885198e0abceb9417edbc9445cd8f431b20c9bb0f92c00c0203801cbed8dcd5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c9af5df8c565794fec8077452ea552469d2e9798e91d58bf86c94182dd3f6fa",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_check_mode/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f3258d0c2a9a2c2d0d72f878e17eb3004a6a8077c2dfdd18f6d7b81b4f7b3241",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_cluster_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_cluster_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_cluster_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b78e9afdbf59243a6b1b668c12ccc83e368ad7b941af028a2fe8b801e25510e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_cluster_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1bc63533af0fc1dfdb758a018542e3a0413ca8c99f4a57fe9fb6fc21bd5f4da8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2cd1eed0121342f03ac6c44df0ee5d679a32e773e6d10f5f9e5b086c2599c97c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/ansible",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/ansible/collection.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ee92d71eac83507748578826b70b4c7627c62fd762d25a5a1b9c6e01fcb3389b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/ansible/module.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "31f6e65cb2317fe8d9e948ed1b41cfe276513fad87c89dcc7f7dc777e76f3f52",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/teams",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/teams/ansible.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cfd95d6ae409c10e16b246437c6c397fe296b3c6ae5259ae6f97e61b8b733e98",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/data/file.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6331d720a3a0ffdeed6f17cee165f55167ce05454d11c680105892a5ea5370c7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/archive.tar",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "66ea86ef8daa23872007eeeee592167eff42d97bf736d889fb04a863b17d66fd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/simple_file.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91e98678271aa6f939eae03a12d5e211da8ec60504f45b0a8fbd5c834881f2a0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/files/simple_zip_file.txt.gz",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67c773560130902e8731322c204334a51d43866c81923f1564a9f84f46953b09",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/library",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/library/k8s_create_file.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "93f30185ab7c1555eedf7d2a1644459e429098a5a6cd29b655f46e5e986b80d4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/library/kubectl_file_compare.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b0ac3c26cf1fa994d208e31fddd30dc0df0efc4a8d759e6dc4be8065a743b7fc",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a3b4b464a995f6bb6c6461efbc9ffc435ce83e9e46767bffeb1c21857b3c51db",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "be8a7eded9036e00f1e0d9cba288079418a203ed5f927cdf7324daf1b17ffc5d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_check_mode.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bea6efecb7b4575c39c18c506d6afed5d565299cef9fbcd3db6c3885d61a4889",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_copy_directory.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "443685ee28f1595bf8a5db62db1493c652a75a2c8c5d7a65facf8f197c841be6",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0d880792d8a1bdd6509556506076119bd8ff1ff594e1db87b463bd3e05ce8f64",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_copy_file.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4281ff6283c5eb885d44cc71d0f13bdd87d37bbdb91cda0b776a954725b6f1cf",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_copy_item_with_space_in_its_name.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c27c6d900440350ee03fa8fc686eb892e95e170f62c70ea9aedb3b35b5ff5531",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_copy_large_file.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91ac7bdf37253df75fc425266714bacc2a99a3d69d126429851f816b152a26e2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/tasks/test_multi_container_pod.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1884a99406cfc2a806e67c74360e7da4dde4cbe2744869a1250008f04e41c006",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/templates/pods_definition.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4d03bf465ba7fd8f9412fb793b654fbbcfbb7cba1f8078d03e928238b922f8cb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_copy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6c0ba901936063300418a5904f39e93b53d8f14c464f54aa413979dbdd156a08",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a12c194c91806fbf129a9c2409cf7244c53fff7c68e57ec140607abea3c3e6cd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/files/crd-resource.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4921362ac3c4afac5f42ebb90b37bcb75e1fe20929bb0e45d0df4c190d28f577",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/files/setup-crd.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "509878fff22a19715f1c491930eefd23430c0f571716b463c3ab9a754d0fb250",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6ead0bbf3914807bf845763335db55c924c3a94c1db565b7527a863b303ee430",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_crd/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "23c561373551c9f466bf4d6ffb9bc09f4151f45d8ac6fc334efe267e4e6f4765",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a0f8a14d3cc752ecea42c850d8886d616de0a54ec5c458f4683e97368b31959f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1b24137238beb8cc3cbfb0a6bd54bf7edbc47c888d5386955e62f23d5269d593",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_delete/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2a0bfed7c9118fefdb2369a00609c684eb852aa1a015a7d787125957a0604a9c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "61b856bc0efc6f72a431f5e38f991fcdeb111cc0e82f08e07bf2ef29f9517ca2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3a199725b66d98cbb7e889ad8e6322268279af39dddb998df20b0807e3582de3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/templates/pod.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3da43f180f8b1af880478c8006f25698bade02f3bff3efe024c3b49b56b1be75",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_diff/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c205fbe9f38acf9d7fc18eb7576971ece1bfdf9e1a47b60963f0dcbca5a44f5b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6ab9dcab4b9b629bf9c9edb77ecbf75acc0df7a99f7c05170b3ba9cebbcde1d4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "497fcfe822f26afc3b5f3841848ad69bd0e937c6d820833e956f64961d6e6d2f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_drain/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a31ef70474df899c4c8fd9d794e46af5937059643b057922bcb87593fc11bb86",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aad2b15b929176487ad0f0bcf1bd99d9bd3ab9f0800ecd6205c9f8121a1714ed",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9da1e458f316283c6e6bfd44e182a975dfaba2e89b6f502d201669239c4942ac",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_exec/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a6f83b7741b2c23e92fa0f5d21c9204aee193182a5ab241f894d54f0ee6664e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "695e74055dc707b2e3aff07a7b0ac58454cce6df4e2156830e8ba6404f8e5274",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "706a0802afe247b21d705adcf9438b038b0d27a20202880fd729aaa46ee4e419",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "07c32378aca9d123a4225e6511d744136e4724c17d3dc01411be2f4f85e70d5f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_full/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "472e6e44d7e3935067a0b8f378d9972f3de5fa8eaab569d753972286128c49d3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3b7da29b5dae95612c694eb918cb3b56a3f1f2cff323622903f0bc2f700e40aa",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c934fafe64f035864ab730f94a881c1a7da97623f307c9b48897907e7524e213",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_gc/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1f1e71ef536c418b80b3c89bb739c762c5a52cf5533e80aa286cd5ff3e168cb1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_generate_name",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_generate_name/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_generate_name/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "55c28b0a6d904883e95e8bb012ab64942e3ddbe0377d36843fc7a73815e4aa82",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_generate_name/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "412084ec88b9ab491edd7114db00d72f0e139d10ac3d31e08045e0a5478b2366",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9b329b4edb06334a5cbe4b1118799ca0592a177e43e040336a68fa27ec409fe1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/tasks/api-server-caching.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70f2353923430f02fc3d214a3f1adbcd1ed61596c658074890ce3e3749cc720d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f4b0280dda29909d09c166ef0a976a405593b8536cc158ea6bf75d4841369dc7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/tasks/wait.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "396d2ee6262c763600d91b36df4815a8d7c4981337ddf4fedf0f93c61eaf6c09",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8ed78ec8cbde6a6dfe3dec44b004648c8215c0861e26a84e145378c9b38149cf",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "524da1ad18f4157306dd9cdf21cf680295a422848f41022a344827f2b26b5c28",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "64961ac3f927d5f12a1033656910abd24d1c3d680fd6b02e774fb320a114c833",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_json_patch/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "22be02d0683dc05d3d4b3595a568e93433c80be1bad902c40f9db6205047143e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9436e442e65820931406d29f4d87a0ebd3d42cd9b9d1cff92c475caa573af9c1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e7762bcc85bd00416a723b2774bedd61d569e36bf1ecd53296ce696d6e439b5b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_label_selectors/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "654de33efd4ce3d3b4022bda5bf2f368f7710040b634e7862e3743fdf320e79f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "585b19e6e2307bac28211a83a843e46587e4bb64434922ab1bfd46e71817334a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b2de720b3dc5adc96ea2de9f56aeb7c671eb5717d60c04f3dabdb397219ff16",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_lists/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dc5cd39b9881b9b8492b587b76b93403d7d715d45fab3b12d4475e6ef591c678",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "98be34ec13b636ae16821e0fce1badbd91780188a2ac5392099c9cde89a53ea3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "22a4a75d97f3d7551f2b86c4d2178640357fa575ddd0ce980fa2d545e042842c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_log/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d3f34076ef04de7f3c32b6a2240b8b9da7456f3e9d7a95badfef14f88339087",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dfa5fc684930a27110a4c60eca6821a3102b9fd06e0573a139c5353ee3e3599b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ebb90920b3deef18b4932f9ec240898d09d173f2e4e2b1248cf77cae5264874",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da9b6909348a797d59e02f1e612e58f3ae54e2d597e264aed30482734c55094d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_manifest_url/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "77e2a017cb4c6bbb4c4010a92cdee2e52c26e15671c521d501d1d7823a5374fc",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b8b822783619e55604574f022da0e6ed37834bf4dd5e0f23141d71130e1a8bd5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "34bd00a6aaaa4ab66781553e4e255c09bd5e713a2edef845dcc9c158a9a025e8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_merge_type/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "45a2243f71e6555a61f5019887d5b8c89f8bc502b751820863e461efaeb98404",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2d6984fc4d87dec00389da9bf9c7ef3d474f70326a7f44a622ba1cadc90f50ec",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2fae65508a87db152a9cfbe2f9265c6ab3e316fe0c8f99d63ec88881a06bd47d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "255128481c38ce5a0e0c53047a5b111156aba224d31f9bd395408f6d02e561f1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_patched/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8952ed443607c851edaf37e4ef3f150fab1d3180cdbdb6d7833d270a06e3a3cd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "86a1692a3383c8df8a72ea9b1fcbe6ef0153ed0c792f2b365ab97d832e61ee80",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "20843a31b17b7fd10c7e55ceddfdc1a0f8da8d928a05cd138ccf24a6dea0128e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_rollback/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "df87fd2e6b8022198931711f0d0356f940057ea3f900b5b15d3543f837480ee5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bc5a7447c9af668aa3c070711ae0d04aac73126d2606b9494d170f3a54331ba0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/files",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/files/deployment.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7aace180b64cf64511b9d3f32d42e177e8f7dac0c7c752ac104852a20146c332",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "af632f359f1507bfc0a8e4eb1b2b36e1222a06ae21859d7c9dfc83da19a37118",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_scale/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "06781a75febe39f510b5228928025aed8268cb9e10f0c54d5fd9cb3b1fdb416b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1e7c37e4067db67fe54f46551fe12d845a9e42bf640942cc01b37f9977532e6e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91e0d8bf83a03733a3d875d635a15d2788238c3371d30abdcb97005cc9152c10",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_taint/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a1cf831f5a94b5f55971cadc974d6f9d61f8260a66f5a0ebd9b55922ff3f6de9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "175b6fd740625da1725068a851364a84b6ece070beb0d5a9f22933dd9d765df8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bf3597ac3979f659fdd8ebdb35d9851238eff3cea0333f85a4105ec19a03cf42",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates/configmap.yml.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cd827ce6e8e1d83551da796b8114d56c3b631d8af5dbec27e649b4e5da43978c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates/pod_one.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f406fafaee09dfaa626acfa30bdca5a211e3a23c5ce9bdbd709dfad382636e9f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates/pod_three.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a9260813cfbc5f9c354da71c35c7bfb4a0bb3854448bd3172a5c672ca58cc827",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates/pod_two.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "41da088ff2b14211959cade261eeb291e7b4aea00ff3a3deb76b1595d795a221",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/templates/pod_with_bad_namespace.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2d47b2e060a0ffe2fb0ec36f9e9fae51a2d3bfed0a1527def18cb4ce7a9d5a0c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_template/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "db26c789a46be23ef24852943678fe46f7f48a774055c8e3ba6dc826952d073d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ec5086725790883056015ccc7ac65e8e46eb2db662632b387edbf9c6d95374b7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "378799cbb98a365b7559e9f6ff9a8ed6cf2abcff97d4ea47a5cd221f795cf12b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_user_impersonation/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "677cabf2fbc556f8d5a4e36908af1056de9790c9805b8b75a5a66cca66b5cdfa",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "36c6277f8c8b62d2f53390afb0305c484f9b8c7496aeba894dd5e6739f0334a3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6cbec7acc2026e44531f73245f00878f03a52bd4cb0c774335c628a421cb9f28",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_validate/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6fa0cf6146c6cf230ad5a94dcd9b8a431ea32ff4949f95305c2fa5f42a322cf2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0132e1ae45ede9940d047ecadb7b81abe95b87f0d0e84a4b79b4db4a95659179",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f5a9e3908edbba802a64be408d037cd4bf60bc790b9c0292ce20d8affd7832ac",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/k8s_waiter/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ca396e8f0b136c71381c48a4b8d524bde59563ce74651a5dd5ede0b0a387852c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3fcae450da9465d6b5dc2d5f89a6aa2a7141250ff66eb19656be5047ac273801",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2fae65508a87db152a9cfbe2f9265c6ab3e316fe0c8f99d63ec88881a06bd47d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9a868413c3830277cdb94c9d8bc2df9a330e44e304b90687953112e677b8b03c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_k8s/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "02e8e6384636b111fe80a3ed7d95ae88b45e43f441fe85398e03fff177d24a5d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0c969831fe1c4a67d534489b3fe52560495b3a45498933f8885bca4ed0c0a336",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5f181f2a4fac30b6ab1513fe8607539e30b4d95d68d7de29a56905844f28998",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0a20b1f483f8fb3d48c6e39c6adb6e122cc0ef44dd826aceaa55b269553f7a54",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/lookup_kustomize/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f63699f10511a8f34dc30149c03cbff800713418d9be0a5ff4192dff5a614e5e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/remove_namespace",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/remove_namespace/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/remove_namespace/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "35bf3578a016147b01d233717f5c031a2f2bbcc24385a89ef355289a08cda2ea",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/remove_namespace/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5ea375becd3088862c16fc97fe379532c583079829fcf1fdcb549e6808262fb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3524e085b6d13b400bb15c8b52c45869348fceba03ed61c1ab1bb05396a4ebe1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/library",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "50fa9ab8aca36af544a370d3c2b5d3b557bd848d0d9ae1f0e751634ea75bbb6b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3a0eaae728b0f81b1ca7b308fe0b89c5985cddd0de4127dc113ef8c3edd8b14e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_kubeconfig/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5ea375becd3088862c16fc97fe379532c583079829fcf1fdcb549e6808262fb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f52d711103d50a437830c6fbcd04fb4bab49a0f82f6d26d1c791c6e8488dd090",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/tasks/create.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "adf69a27e38631719bd163dc0181e685fe769148a82f1319cd24f0486c7d2541",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "17cc7f98717af53c169f83f5beb784c023b02c0a1c82ce626d63557362826c98",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_namespace/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5ea375becd3088862c16fc97fe379532c583079829fcf1fdcb549e6808262fb",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.10.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "df0567067fd935ff36eae1b6952a83b392be1c333519821a3af56437f4a55a2f",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.11.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70624e6d15f87ec118ba71c135b060431f04aa3746123666e8cb951b9cb1bca1",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.12.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c108894b090a50da8f307726409f3055b481d5ee255f2932c1d52bae72d50058",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.13.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c108894b090a50da8f307726409f3055b481d5ee255f2932c1d52bae72d50058",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.14.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c108894b090a50da8f307726409f3055b481d5ee255f2932c1d52bae72d50058",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.15.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "459348e18aabc4f5a164df89f028a7c736551ad166e14e24924a209cab0e52bf",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.9.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f81ba55b45ea34f4fe65ecaba048fb917867bd7b87b020069ddb83f145179391",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/refresh_ignore_files",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f4caf94f5cdc9e95d6761febbbd1a5b0632777c5fe1baf12e070c172da887d65",
+ "format": 1
+ },
+ {
+ "name": "tests/unit",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/action",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/action/test_remove_omit.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "30e4e2521f84b3bd3a408ebb75c3adeeb0e99382bd9f34afc461ae6778fa5683",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/fixtures",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/fixtures/definitions.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a800f11cdae06784834a41a93a60f866320ffe44a6047b11ce98ce528ce3fabe",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/fixtures/deployments.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6033cce016f1619d27553a452bcd66c43ea83cb78e0b34bd63881dd0888b97df",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/fixtures/pods.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "984527710b247eae4b144baf9bcd1068d6c481ea9492a3031a316a10d40bfa51",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_apply.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "28703ed0cab23068fbbd0d99ba1095ca2c2c484f28fa25c05716ed29671c2ac5",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_client.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "84829637b1d0d7210f75b3c33210dde9d1233230563b9fcf0557695787e9f700",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_common.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "de8c6088bf3f034ea167b86cb22adea982c4bd1320d0eccd3771b053f9481a05",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_core.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "25d001234f6af4441e1fd3e75b9a691f0b016662000e898d7603879ec01eb57e",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_discoverer.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0517cb0710d18a3730e675e6206e21fc50ec98c6338eb25547838d4cf107dd87",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_hashes.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3d01aed53815b18dc89fd1373975261e5a36f99ef2fba9a86e28406bc103309c",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_helm.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e7b1d5eec28c07a73b0e71b67bf6211496adb11c9ff748f7bb47e7c5bc279ed4",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_marshal.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3aa6c8559019f59af3518fcb63c92feb4bbd115ae052d35e0569e992421508ac",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_resource.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "add99dd95ed20a3352d53eb1357fb84fd38a6ecacc2cce4674172840dd062209",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_runner.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bf805b42bed1e709ce7d2b2bb2139d0b9014ba3a712168b0095f78f4143217e4",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67deb919d8c08d09b06a4926fe0460ef98404eb833cb11ffa0d16a77c0f8bda6",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_service.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "22eb9a8b500a62548486b754dacccd40f92e612659705cda953c68e95178a802",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/module_utils/test_waiter.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a83e8cf7a00c1f6f6d9b08fcb01a9b0807f3d59081c4697da0bec1908a1a6eb1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/modules/test_helm_template.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "add6e070d2495e7762db03cd967ea526c3b722f7cd9e0d91ecf682f734600912",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/modules/test_helm_template_module.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ce4462f8dc0e6f61abd6ed59951d95ed57aac564da92b494d23d31c963ce5df6",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/modules/test_module_helm.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "65480cc9a747d2f5d7f14e9936f9c53ca4cd781b1e01c9d739304795d0c8d259",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit/utils/ansible_module_mock.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "26336dbd51c1c5644abb4bcc51ffc18f4b1c03c71ac0a9430b2135edf8132832",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/conftest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e71c6426972ffd4826b7d5fb7f021333ca0760ea442b9e549b0916e837cd54c7",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9786cc70a5b99e738917f0b25684cf4caa752c01d7c298be0d3526af282a0346",
+ "format": 1
+ },
+ {
+ "name": "tests/config.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9a009a349eaaf78c93ff56072d2ef171937bdb884e4976592ab5aaa9c68e1044",
+ "format": 1
+ },
+ {
+ "name": ".gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5d56adf26b6ed553559348325b50747c3b7d664cdd59982cb438016cbe8ce75c",
+ "format": 1
+ },
+ {
+ "name": ".yamllint",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3502ec6ac2192fb928af5c4e85df274d577553d4eb533f6f37c0957953cbec2a",
+ "format": 1
+ },
+ {
+ "name": "CHANGELOG.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e7d76b349ab603ebc12c9b49cf98017e88a9b8ba1bda3a26a00aa664b9fb2594",
+ "format": 1
+ },
+ {
+ "name": "CONTRIBUTING.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5a96179224855c796561d451fd8eb4430a8ec054ef34ad3201423b8e7fa4c75f",
+ "format": 1
+ },
+ {
+ "name": "LICENSE",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b1ba204bb69a0ade2bfcf65ef294a920f6bb361b317dba43c7ef29d96332b9b",
+ "format": 1
+ },
+ {
+ "name": "Makefile",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bec33eed95fe1d63e3fb0a9085318d6916c24eb6de43f0c34ed9dc4e3b678c50",
+ "format": 1
+ },
+ {
+ "name": "PSF-license.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "83b042fc7d6aca0f10d68e45efa56b9bc0a1496608e7e7728fe09d1a534a054a",
+ "format": 1
+ },
+ {
+ "name": "README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "96e8d2d129338de73a45a8957e505822a152b8265c870fb6a92cd6e35bb4e30d",
+ "format": 1
+ },
+ {
+ "name": "bindep.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "488be8bac2e9a5d7b4518cc1cbc71f1cf5bd7e080da852f673c45b4f68f6467a",
+ "format": 1
+ },
+ {
+ "name": "codecov.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "caa848a2e02be5014890c5cbc7727e9a00d40394637c90886eb813d60f82c9c3",
+ "format": 1
+ },
+ {
+ "name": "requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f4d1053cac2a14673a9faf001292589693d10f6126b23255e34beb8223d1de91",
+ "format": 1
+ },
+ {
+ "name": "setup.cfg",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91352cd0180469f3b0114c839c6d1df890fa8599c411346f30461a7e4490fbfd",
+ "format": 1
+ },
+ {
+ "name": "test-requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5f2d972c940f041edc9dd2affe30bf4dd1543e35a471125dafce1f5b64026767",
+ "format": 1
+ },
+ {
+ "name": "tox.ini",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "63b3c9e7c1e29e051dde455446312eaededeee7006c4e5e23746049fa7de2255",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/LICENSE b/ansible_collections/kubernetes/core/LICENSE
new file mode 100644
index 00000000..e72bfdda
--- /dev/null
+++ b/ansible_collections/kubernetes/core/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>. \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/MANIFEST.json b/ansible_collections/kubernetes/core/MANIFEST.json
new file mode 100644
index 00000000..37be7493
--- /dev/null
+++ b/ansible_collections/kubernetes/core/MANIFEST.json
@@ -0,0 +1,43 @@
+{
+ "collection_info": {
+ "namespace": "kubernetes",
+ "name": "core",
+ "version": "2.4.0",
+ "authors": [
+ "chouseknecht (https://github.com/chouseknecht)",
+ "geerlingguy (https://www.jeffgeerling.com/)",
+ "maxamillion (https://github.com/maxamillion)",
+ "jmontleon (https://github.com/jmontleon)",
+ "fabianvf (https://github.com/fabianvf)",
+ "willthames (https://github.com/willthames)",
+ "mmazur (https://github.com/mmazur)",
+ "jamescassell (https://github.com/jamescassell)"
+ ],
+ "readme": "README.md",
+ "tags": [
+ "kubernetes",
+ "k8s",
+ "cloud",
+ "infrastructure",
+ "openshift",
+ "okd",
+ "cluster"
+ ],
+ "description": "Kubernetes Collection for Ansible.",
+ "license": [],
+ "license_file": "LICENSE",
+ "dependencies": {},
+ "repository": "https://github.com/ansible-collections/kubernetes.core",
+ "documentation": "",
+ "homepage": "",
+ "issues": "https://github.com/ansible-collections/kubernetes.core/issues"
+ },
+ "file_manifest_file": {
+ "name": "FILES.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67908745863fefe0495eee77399178e88b89bb04a19fc728d2ac83a4d93abd1a",
+ "format": 1
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/Makefile b/ansible_collections/kubernetes/core/Makefile
new file mode 100644
index 00000000..3f7a4184
--- /dev/null
+++ b/ansible_collections/kubernetes/core/Makefile
@@ -0,0 +1,28 @@
+# Also needs to be updated in galaxy.yml
+VERSION = 2.4.0
+
+TEST_ARGS ?= ""
+PYTHON_VERSION ?= `python -c 'import platform; print(".".join(platform.python_version_tuple()[0:2]))'`
+
+clean:
+ rm -f kubernetes-core-${VERSION}.tar.gz
+ rm -rf ansible_collections
+ rm -rf tests/output
+
+build: clean
+ ansible-galaxy collection build
+
+release: build
+ ansible-galaxy collection publish kubernetes-core-${VERSION}.tar.gz
+
+install: build
+ ansible-galaxy collection install -p ansible_collections kubernetes-core-${VERSION}.tar.gz
+
+test-sanity:
+ ansible-test sanity --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
+
+test-integration:
+ ansible-test integration --diff --no-temp-workdir --color --skip-tags False --retry-on-error --continue-on-error --python $(PYTHON_VERSION) -v --coverage $(?TEST_ARGS)
+
+test-unit:
+ ansible-test units --docker -v --color --python $(PYTHON_VERSION) $(?TEST_ARGS)
diff --git a/ansible_collections/kubernetes/core/PSF-license.txt b/ansible_collections/kubernetes/core/PSF-license.txt
new file mode 100644
index 00000000..35acd7fb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/PSF-license.txt
@@ -0,0 +1,48 @@
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff --git a/ansible_collections/kubernetes/core/README.md b/ansible_collections/kubernetes/core/README.md
new file mode 100644
index 00000000..89f52079
--- /dev/null
+++ b/ansible_collections/kubernetes/core/README.md
@@ -0,0 +1,242 @@
+# Kubernetes Collection for Ansible
+
+[![CI](https://github.com/ansible-collections/kubernetes.core/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/kubernetes.core/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/kubernetes.core)](https://codecov.io/gh/ansible-collections/kubernetes.core)
+
+This repository hosts the `kubernetes.core` (formerly known as `community.kubernetes`) Ansible Collection.
+
+The collection includes a variety of Ansible content to help automate the management of applications in Kubernetes and OpenShift clusters, as well as the provisioning and maintenance of clusters themselves.
+
+<!--start requires_ansible-->
+## Ansible version compatibility
+
+This collection has been tested against following Ansible versions: **>=2.9.17**.
+
+For collections that support Ansible 2.9, please ensure you update your `network_os` to use the
+fully qualified collection name (for example, `cisco.ios.ios`).
+Plugins and modules within a collection may be tested with only specific Ansible versions.
+A collection may contain metadata that identifies these versions.
+PEP440 is the schema used to describe the versions of Ansible.
+<!--end requires_ansible-->
+
+## Python Support
+
+* Collection supports 3.6+
+
+Note: Python2 is deprecated from [1st January 2020](https://www.python.org/doc/sunset-python-2/). Please switch to Python3.
+
+## Kubernetes Version Support
+
+This collection supports Kubernetes versions >=1.19.
+
+## Included content
+
+Click on the name of a plugin or module to view that content's documentation:
+
+<!--start collection content-->
+### Connection plugins
+Name | Description
+--- | ---
+[kubernetes.core.kubectl](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.kubectl_connection.rst)|Execute tasks in pods running on Kubernetes.
+
+### K8s filter plugins
+Name | Description
+--- | ---
+kubernetes.core.k8s_config_resource_name|Generate resource name for the given resource of type ConfigMap, Secret
+
+### Inventory plugins
+Name | Description
+--- | ---
+[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_inventory.rst)|Kubernetes (K8s) inventory source
+
+### Lookup plugins
+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
+--- | ---
+[kubernetes.core.helm](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_module.rst)|Manages Kubernetes packages with the Helm package manager
+[kubernetes.core.helm_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_info_module.rst)|Get information from Helm package deployed inside the cluster
+[kubernetes.core.helm_plugin](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_module.rst)|Manage Helm plugins
+[kubernetes.core.helm_plugin_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_plugin_info_module.rst)|Gather information about Helm plugins
+[kubernetes.core.helm_pull](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_pull_module.rst)|download a chart from a repository and (optionally) unpack it in local directory.
+[kubernetes.core.helm_repository](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_repository_module.rst)|Manage Helm repositories.
+[kubernetes.core.helm_template](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.helm_template_module.rst)|Render chart templates
+[kubernetes.core.k8s](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_module.rst)|Manage Kubernetes (K8s) objects
+[kubernetes.core.k8s_cluster_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_cluster_info_module.rst)|Describe Kubernetes (K8s) cluster, APIs available and their respective versions
+[kubernetes.core.k8s_cp](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_cp_module.rst)|Copy files and directories to and from pod.
+[kubernetes.core.k8s_drain](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_drain_module.rst)|Drain, Cordon, or Uncordon node in k8s cluster
+[kubernetes.core.k8s_exec](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_exec_module.rst)|Execute command in Pod
+[kubernetes.core.k8s_info](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_info_module.rst)|Describe Kubernetes (K8s) objects
+[kubernetes.core.k8s_json_patch](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_json_patch_module.rst)|Apply JSON patch operations to existing objects
+[kubernetes.core.k8s_log](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_log_module.rst)|Fetch logs from Kubernetes resources
+[kubernetes.core.k8s_rollback](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_rollback_module.rst)|Rollback Kubernetes (K8S) Deployments and DaemonSets
+[kubernetes.core.k8s_scale](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_scale_module.rst)|Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
+[kubernetes.core.k8s_service](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_service_module.rst)|Manage Services on Kubernetes
+[kubernetes.core.k8s_taint](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/kubernetes.core.k8s_taint_module.rst)|Taint a node in a Kubernetes/OpenShift cluster
+
+<!--end collection content-->
+
+## Installation and Usage
+
+### Installing the Collection from Ansible Galaxy
+
+Before using the Kubernetes collection, you need to install it with the Ansible Galaxy CLI:
+
+ ansible-galaxy collection install kubernetes.core
+
+You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format:
+
+```yaml
+---
+collections:
+ - name: kubernetes.core
+ version: 2.4.0
+```
+
+### Installing the Kubernetes Python Library
+
+Content in this collection requires the [Kubernetes Python client](https://pypi.org/project/kubernetes/) to interact with Kubernetes' APIs. You can install it with:
+
+ pip3 install kubernetes
+
+### Using modules from the Kubernetes Collection in your playbooks
+
+It's preferable to use content in this collection using their Fully Qualified Collection Namespace (FQCN), for example `kubernetes.core.k8s_info`:
+
+```yaml
+---
+- hosts: localhost
+ gather_facts: false
+ connection: local
+
+ tasks:
+ - name: Ensure the myapp Namespace exists.
+ kubernetes.core.k8s:
+ api_version: v1
+ kind: Namespace
+ name: myapp
+ state: present
+
+ - name: Ensure the myapp Service exists in the myapp Namespace.
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: myapp
+ namespace: myapp
+ spec:
+ type: LoadBalancer
+ ports:
+ - port: 8080
+ targetPort: 8080
+ selector:
+ app: myapp
+
+ - name: Get a list of all Services in the myapp namespace.
+ kubernetes.core.k8s_info:
+ kind: Service
+ namespace: myapp
+ register: myapp_services
+
+ - name: Display number of Services in the myapp namespace.
+ debug:
+ var: myapp_services.resources | count
+```
+
+If upgrading older playbooks which were built prior to Ansible 2.10 and this collection's existence, you can also define `collections` in your play and refer to this collection's modules as you did in Ansible 2.9 and below, as in this example:
+
+```yaml
+---
+- hosts: localhost
+ gather_facts: false
+ connection: local
+
+ collections:
+ - kubernetes.core
+
+ tasks:
+ - name: Ensure the myapp Namespace exists.
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: myapp
+ state: present
+```
+
+For documentation on how to use individual modules and other content included in this collection, please see the links in the 'Included content' section earlier in this README.
+
+## Ansible Turbo mode Tech Preview
+
+
+The ``kubernetes.core`` collection supports Ansible Turbo mode as a tech preview via the ``cloud.common`` collection. By default, this feature is disabled. To enable Turbo mode for modules, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. For example:
+
+```yaml
+---
+- hosts: remote
+ environment:
+ ENABLE_TURBO_MODE: 1
+ tasks:
+ ...
+```
+
+To enable Turbo mode for k8s lookup plugin, set the environment variable `ENABLE_TURBO_MODE=1` on the managed node. This is not working when
+defined in the playbook using `environment` keyword as above, you must set it using `export ENABLE_TURBO_MODE=1`.
+
+Please read more about Ansible Turbo mode - [here](https://github.com/ansible-collections/kubernetes.core/blob/main/docs/ansible_turbo_mode.rst).
+
+## Testing and Development
+
+If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there.
+
+See [Contributing to kubernetes.core](CONTRIBUTING.md).
+
+### Testing with `ansible-test`
+
+The `tests` directory contains configuration for running sanity and integration tests using [`ansible-test`](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html).
+
+You can run the collection's test suites with the commands:
+
+ make test-sanity
+ make test-integration
+ make test-unit
+
+### Testing with `molecule`
+
+There are also integration tests in the `molecule` directory which are meant to be run against a local Kubernetes cluster, e.g. using [KinD](https://kind.sigs.k8s.io) or [Minikube](https://minikube.sigs.k8s.io). To setup a local cluster using KinD and run Molecule:
+
+ kind create cluster
+ make test-molecule
+
+## Publishing New Versions
+
+Releases are automatically built and pushed to Ansible Galaxy for any new tag. Before tagging a release, make sure to do the following:
+
+ 1. Update the version in the following places:
+ 1. The `version` in `galaxy.yml`
+ 2. This README's `requirements.yml` example
+ 3. The `VERSION` in `Makefile`
+ 2. Update the CHANGELOG:
+ 1. Make sure you have [`antsibull-changelog`](https://pypi.org/project/antsibull-changelog/) installed.
+ 2. Make sure there are fragments for all known changes in `changelogs/fragments`.
+ 3. Run `antsibull-changelog release`.
+ 3. Commit the changes and create a PR with the changes. Wait for tests to pass, then merge it once they have.
+ 4. Tag the version in Git and push to GitHub.
+
+After the version is published, verify it exists on the [Kubernetes Collection Galaxy page](https://galaxy.ansible.com/kubernetes/core).
+
+The process for uploading a supported release to Automation Hub is documented separately.
+
+## More Information
+
+For more information about Ansible's Kubernetes integration, join the `#ansible-kubernetes` channel on [libera.chat](https://libera.chat/) IRC, and browse the resources in the [Kubernetes Working Group](https://github.com/ansible/community/wiki/Kubernetes) Community wiki page.
+
+## License
+
+GNU General Public License v3.0 or later
+
+See LICENCE to see the full text.
diff --git a/ansible_collections/kubernetes/core/bindep.txt b/ansible_collections/kubernetes/core/bindep.txt
new file mode 100644
index 00000000..3c4bb279
--- /dev/null
+++ b/ansible_collections/kubernetes/core/bindep.txt
@@ -0,0 +1,3 @@
+kubernetes-client [platform:fedora]
+openshift-clients [platform:rhel-8]
+openshift-clients [platform:rhel-9]
diff --git a/ansible_collections/kubernetes/core/changelogs/changelog.yaml b/ansible_collections/kubernetes/core/changelogs/changelog.yaml
new file mode 100644
index 00000000..f070f510
--- /dev/null
+++ b/ansible_collections/kubernetes/core/changelogs/changelog.yaml
@@ -0,0 +1,764 @@
+ancestor: null
+releases:
+ 0.10.0:
+ changes:
+ bugfixes:
+ - k8s - Add exception handling when retrieving k8s client (https://github.com/ansible-collections/community.kubernetes/pull/54).
+ - k8s - Fix argspec for 'elements' (https://github.com/ansible-collections/community.kubernetes/issues/13).
+ - k8s - Use ``from_yaml`` filter with lookup examples in ``k8s`` module documentation
+ examples (https://github.com/ansible-collections/community.kubernetes/pull/56).
+ - k8s_service - Fix argspec (https://github.com/ansible-collections/community.kubernetes/issues/33).
+ - kubectl - Fix documentation in kubectl connection plugin (https://github.com/ansible-collections/community.kubernetes/pull/52).
+ major_changes:
+ - k8s_exec - New module for executing commands on pods via Kubernetes API (https://github.com/ansible-collections/community.kubernetes/pull/14).
+ - k8s_log - New module for retrieving pod logs (https://github.com/ansible-collections/community.kubernetes/pull/16).
+ minor_changes:
+ - k8s - Added ``persist_config`` option for persisting refreshed tokens (https://github.com/ansible-collections/community.kubernetes/issues/49).
+ security_fixes:
+ - kubectl - Warn about information disclosure when using options like ``kubectl_password``,
+ ``kubectl_extra_args``, and ``kubectl_token`` to pass data through to the
+ command line using the ``kubectl`` connection plugin (https://github.com/ansible-collections/community.kubernetes/pull/51).
+ fragments:
+ - 13-fix-elements-argspec.yaml
+ - 14-k8s_exec-new-module.yaml
+ - 16-k8s_log-new-module.yaml
+ - 33-k8s_service-fix-argspec.yaml
+ - 49-k8s-add-persist_config-option.yaml
+ - 51-kubectl-security-disclosure.yaml
+ - 52-kubectl-connection-docsfix.yaml
+ - 54-k8s-add-exception-handling.yaml
+ - 56-k8s-from_yaml-docs-examples.yaml
+ modules:
+ - description: Execute command in Pod
+ name: k8s_exec
+ namespace: ''
+ - description: Fetch logs from Kubernetes resources
+ name: k8s_log
+ namespace: ''
+ release_date: '2020-03-23'
+ 0.11.0:
+ changes:
+ bugfixes:
+ - Make sure extra files are not included in built collection (https://github.com/ansible-collections/community.kubernetes/pull/85).
+ - Update GitHub Actions workflow for better CI stability (https://github.com/ansible-collections/community.kubernetes/pull/78).
+ - k8s_log - Module no longer attempts to parse log as JSON (https://github.com/ansible-collections/community.kubernetes/pull/69).
+ major_changes:
+ - helm - New module for managing Helm charts (https://github.com/ansible-collections/community.kubernetes/pull/61).
+ - helm_info - New module for retrieving Helm chart information (https://github.com/ansible-collections/community.kubernetes/pull/61).
+ - helm_repository - New module for managing Helm repositories (https://github.com/ansible-collections/community.kubernetes/pull/61).
+ minor_changes:
+ - Rename repository to ``community.kubernetes`` (https://github.com/ansible-collections/community.kubernetes/pull/81).
+ fragments:
+ - 61-helm-new-modules.yaml
+ - 69-k8s_log-dont-parse-as-json.yaml
+ - 78-github-actions-workflow.yaml
+ - 81-rename-repository.yaml
+ - 85-exclude-unnecessary-files-when-building.yaml
+ modules:
+ - description: Manages Kubernetes packages with the Helm package manager
+ name: helm
+ namespace: ''
+ - description: Get information from Helm package deployed inside the cluster
+ name: helm_info
+ namespace: ''
+ - description: Add and remove Helm repository
+ name: helm_repository
+ namespace: ''
+ release_date: '2020-05-04'
+ 0.11.1:
+ changes:
+ bugfixes:
+ - Fix suboption docs structure for inventory plugins (https://github.com/ansible-collections/community.kubernetes/pull/103).
+ - Handle invalid kubeconfig parsing error (https://github.com/ansible-collections/community.kubernetes/pull/119).
+ - Make sure Service changes run correctly in check_mode (https://github.com/ansible-collections/community.kubernetes/pull/84).
+ - k8s_info - remove unneccessary k8s_facts deprecation notice (https://github.com/ansible-collections/community.kubernetes/pull/97).
+ - k8s_scale - Fix scale wait and add tests (https://github.com/ansible-collections/community.kubernetes/pull/100).
+ - raw - handle condition when definition is none (https://github.com/ansible-collections/community.kubernetes/pull/139).
+ major_changes:
+ - Add changelog and fragments and document changelog process (https://github.com/ansible-collections/community.kubernetes/pull/131).
+ minor_changes:
+ - Add action groups for playbooks with module_defaults (https://github.com/ansible-collections/community.kubernetes/pull/107).
+ - Add requires_ansible version constraints to runtime.yml (https://github.com/ansible-collections/community.kubernetes/pull/126).
+ - Add sanity test ignore file for Ansible 2.11 (https://github.com/ansible-collections/community.kubernetes/pull/130).
+ - Add test for openshift apply bug (https://github.com/ansible-collections/community.kubernetes/pull/94).
+ - Add version_added to each new collection module (https://github.com/ansible-collections/community.kubernetes/pull/98).
+ - Check Python code using flake8 (https://github.com/ansible-collections/community.kubernetes/pull/123).
+ - Don't require project coverage check on PRs (https://github.com/ansible-collections/community.kubernetes/pull/102).
+ - Improve k8s Deployment and Daemonset wait conditions (https://github.com/ansible-collections/community.kubernetes/pull/35).
+ - Minor documentation fixes and use of FQCN in some examples (https://github.com/ansible-collections/community.kubernetes/pull/114).
+ - Remove action_groups_redirection entry from meta/runtime.yml (https://github.com/ansible-collections/community.kubernetes/pull/127).
+ - Remove deprecated ANSIBLE_METADATA field (https://github.com/ansible-collections/community.kubernetes/pull/95).
+ - Use FQCN in module docs and plugin examples (https://github.com/ansible-collections/community.kubernetes/pull/146).
+ - Use improved kubernetes diffs where possible (https://github.com/ansible-collections/community.kubernetes/pull/105).
+ - helm - add 'atomic' option (https://github.com/ansible-collections/community.kubernetes/pull/115).
+ - helm - minor code refactoring (https://github.com/ansible-collections/community.kubernetes/pull/110).
+ - helm_info and helm_repository - minor code refactor (https://github.com/ansible-collections/community.kubernetes/pull/117).
+ - k8s - Handle set object retrieved from lookup plugin (https://github.com/ansible-collections/community.kubernetes/pull/118).
+ fragments:
+ - 100-k8s_scale-fix-wait.yaml
+ - 102-dont-require-codecov-check-prs.yaml
+ - 103-fix-inventory-docs-structure.yaml
+ - 105-improved-k8s-diffs.yaml
+ - 107-action-groups-module_defaults.yaml
+ - 110-helm-minor-refactor.yaml
+ - 114-minor-docs-fixes.yaml
+ - 115-helm-add-atomic.yaml
+ - 117-helm-minor-refactor.yaml
+ - 118-k8s-lookup-handle-set-object.yaml
+ - 119-handle-kubeconfig-error.yaml
+ - 123-flake8.yaml
+ - 126-requires_ansible-version-constraints.yaml
+ - 127-remove-action_groups_redirection.yaml
+ - 130-add-sanity-ignore-211.yaml
+ - 131-changelog-fragments.yaml
+ - 139-fix-manifest-ends-with-separator.yml
+ - 146-fqcn-in-docs.yaml
+ - 35-wait-conditions.yaml
+ - 84-check_mode-service-change.yaml
+ - 94-openshift-apply-test.yaml
+ - 95-remove-ANSIBLE_METADATA.yaml
+ - 97-remove-k8s_facts-deprecation.yaml
+ - 98-add-version_added.yaml
+ release_date: '2020-07-01'
+ 0.9.0:
+ changes:
+ major_changes:
+ - k8s - Inventory source migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s - Lookup plugin migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s - Module migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s_auth - Module migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s_config_resource_name - Filter plugin migrated from Ansible 2.9 to Kubernetes
+ collection.
+ - k8s_info - Module migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s_scale - Module migrated from Ansible 2.9 to Kubernetes collection.
+ - k8s_service - Module migrated from Ansible 2.9 to Kubernetes collection.
+ - kubectl - Connection plugin migrated from Ansible 2.9 to Kubernetes collection.
+ - openshift - Inventory source migrated from Ansible 2.9 to Kubernetes collection.
+ fragments:
+ - 4-k8s-prepare-collection-for-release.yaml
+ release_date: '2020-02-05'
+ 1.0.0:
+ changes:
+ bugfixes:
+ - Test against stable ansible branch so molecule tests work (https://github.com/ansible-collections/community.kubernetes/pull/168).
+ - Update openshift requirements in k8s module doc (https://github.com/ansible-collections/community.kubernetes/pull/153).
+ major_changes:
+ - helm_plugin - new module to manage Helm plugins (https://github.com/ansible-collections/community.kubernetes/pull/154).
+ - helm_plugin_info - new modules to gather information about Helm plugins (https://github.com/ansible-collections/community.kubernetes/pull/154).
+ - k8s_exec - Return rc for the command executed (https://github.com/ansible-collections/community.kubernetes/pull/158).
+ minor_changes:
+ - Ensure check mode results are as expected (https://github.com/ansible-collections/community.kubernetes/pull/155).
+ - Update base branch to 'main' (https://github.com/ansible-collections/community.kubernetes/issues/148).
+ - helm - Add support for K8S_AUTH_CONTEXT, K8S_AUTH_KUBECONFIG env (https://github.com/ansible-collections/community.kubernetes/pull/141).
+ - helm - Allow creating namespaces with Helm (https://github.com/ansible-collections/community.kubernetes/pull/157).
+ - helm - add aliases context for kube_context (https://github.com/ansible-collections/community.kubernetes/pull/152).
+ - helm - add support for K8S_AUTH_KUBECONFIG and K8S_AUTH_CONTEXT environment
+ variable (https://github.com/ansible-collections/community.kubernetes/issues/140).
+ - helm_info - add aliases context for kube_context (https://github.com/ansible-collections/community.kubernetes/pull/152).
+ - helm_info - add support for K8S_AUTH_KUBECONFIG and K8S_AUTH_CONTEXT environment
+ variable (https://github.com/ansible-collections/community.kubernetes/issues/140).
+ - k8s_exec - return RC for the command executed (https://github.com/ansible-collections/community.kubernetes/issues/122).
+ - k8s_info - Update example using vars (https://github.com/ansible-collections/community.kubernetes/pull/156).
+ security_fixes:
+ - kubectl - connection plugin now redact kubectl_token and kubectl_password
+ in console log (https://github.com/ansible-collections/community.kubernetes/issues/65).
+ - kubectl - redacted token and password from console log (https://github.com/ansible-collections/community.kubernetes/pull/159).
+ fragments:
+ - 122_k8s_exec_rc.yml
+ - 140-kubeconfig-env.yaml
+ - 141-helm-add-k8s-env-vars.yaml
+ - 148-update-base-branch-main.yaml
+ - 152-helm-context-aliases.yml
+ - 153-update-openshift-requirements.yaml
+ - 154-helm_plugin-helm_plugin_info-new-modules.yaml
+ - 155-ensure-check-mode-waits.yaml
+ - 156-k8s_info-vars-example.yaml
+ - 157-helm-create-namespace.yaml
+ - 158-k8s_exec-return-rc.yaml
+ - 159-kubectl-redact-token-and-password.yaml
+ - 168-test-stable-ansible.yaml
+ - 65_kubectl.yml
+ modules:
+ - description: Manage Helm plugins
+ name: helm_plugin
+ namespace: ''
+ - description: Gather information about Helm plugins
+ name: helm_plugin_info
+ namespace: ''
+ release_date: '2020-07-28'
+ 1.1.0:
+ changes:
+ bugfixes:
+ - common - handle exception raised due to DynamicClient (https://github.com/ansible-collections/community.kubernetes/pull/224).
+ - helm - add replace parameter (https://github.com/ansible-collections/community.kubernetes/issues/106).
+ - k8s (inventory) - Set the connection plugin and transport separately (https://github.com/ansible-collections/community.kubernetes/pull/208).
+ - k8s (inventory) - Specify FQCN for k8s inventory plugin to fix use with Ansible
+ 2.9 (https://github.com/ansible-collections/community.kubernetes/pull/250).
+ - k8s_info - add wait functionality (https://github.com/ansible-collections/community.kubernetes/issues/18).
+ major_changes:
+ - k8s - Add support for template parameter (https://github.com/ansible-collections/community.kubernetes/pull/230).
+ - k8s_* - Add support for vaulted kubeconfig and src (https://github.com/ansible-collections/community.kubernetes/pull/193).
+ minor_changes:
+ - Add Makefile and downstream build script for kubernetes.core (https://github.com/ansible-collections/community.kubernetes/pull/197).
+ - Add execution environment metadata (https://github.com/ansible-collections/community.kubernetes/pull/211).
+ - Add probot stale bot configuration to autoclose issues (https://github.com/ansible-collections/community.kubernetes/pull/196).
+ - Added a contribution guide (https://github.com/ansible-collections/community.kubernetes/pull/192).
+ - Refactor module_utils (https://github.com/ansible-collections/community.kubernetes/pull/223).
+ - Replace KubernetesAnsibleModule class with dummy class (https://github.com/ansible-collections/community.kubernetes/pull/227).
+ - Replace KubernetesRawModule class with K8sAnsibleMixin (https://github.com/ansible-collections/community.kubernetes/pull/231).
+ - common - Do not mark task as changed when diff is irrelevant (https://github.com/ansible-collections/community.kubernetes/pull/228).
+ - helm - Add appVersion idempotence check to Helm (https://github.com/ansible-collections/community.kubernetes/pull/246).
+ - helm - Return status in check mode (https://github.com/ansible-collections/community.kubernetes/pull/192).
+ - helm - Support for single or multiple values files (https://github.com/ansible-collections/community.kubernetes/pull/93).
+ - helm_* - Support vaulted kubeconfig (https://github.com/ansible-collections/community.kubernetes/pull/229).
+ - k8s - SelfSubjectAccessReviews supported when 405 response received (https://github.com/ansible-collections/community.kubernetes/pull/237).
+ - k8s - add testcase for adding multiple resources using template parameter
+ (https://github.com/ansible-collections/community.kubernetes/issues/243).
+ - k8s_info - Add support for wait (https://github.com/ansible-collections/community.kubernetes/pull/235).
+ - k8s_info - update custom resource example (https://github.com/ansible-collections/community.kubernetes/issues/202).
+ - kubectl plugin - correct console log (https://github.com/ansible-collections/community.kubernetes/issues/200).
+ - raw - Handle exception raised by underlying APIs (https://github.com/ansible-collections/community.kubernetes/pull/180).
+ fragments:
+ - 106-helm_replace.yml
+ - 180_raw_handle_exception.yml
+ - 18_k8s_info_wait.yml
+ - 191_contributing.yml
+ - 192_helm-status-check-mode.yml
+ - 193_vault-kubeconfig-support.yml
+ - 196_probot-stale-bot.yml
+ - 197_downstream-makefile.yml
+ - 200_kubectl_fix.yml
+ - 202_k8s_info.yml
+ - 208_set-connection-plugin-transport.yml
+ - 211_execution-env-meta.yml
+ - 223_refactor-module_utils.yml
+ - 224_handle-dynamicclient-exception.yml
+ - 227_replace-kubernetesansiblemodule-class.yml
+ - 228_dont-mark-changed-if-diff-irrelevant.yml
+ - 229_helm-vault-support.yml
+ - 230_k8s-template-parameter.yml
+ - 231_k8sansiblemixin-module.yml
+ - 234_k8s-selfsubjectaccessreviews.yml
+ - 235_k8s_info-wait-support.yml
+ - 243_template.yml
+ - 246_helm-appversion-check.yml
+ - 252_connection-plugin-fqcn-fix.yml
+ - 93_helm-multiple-values-files.yml
+ release_date: '2020-10-08'
+ 1.1.1:
+ changes:
+ bugfixes:
+ - k8s - Fix sanity test 'compile' failing because of positional args (https://github.com/ansible-collections/community.kubernetes/issues/260).
+ fragments:
+ - 260_k8s-positional-args.yml
+ release_date: '2020-10-09'
+ 1.2.0:
+ changes:
+ bugfixes:
+ - helm - ``release_values`` makes ansible always show changed state (https://github.com/ansible-collections/community.kubernetes/issues/274)
+ - helm - make helm-diff plugin detection more reliable by splitting by any whitespace
+ instead of explicit whitespace (``\s``) (https://github.com/ansible-collections/community.kubernetes/pull/362).
+ - helm - return values in check mode when release is not present (https://github.com/ansible-collections/community.kubernetes/issues/280).
+ - helm_plugin - make unused ``release_namespace`` parameter as optional (https://github.com/ansible-collections/community.kubernetes/issues/357).
+ - helm_plugin_info - make unused ``release_namespace`` parameter as optional
+ (https://github.com/ansible-collections/community.kubernetes/issues/357).
+ - k8s - fix check_mode always showing changes when using stringData on Secrets
+ (https://github.com/ansible-collections/community.kubernetes/issues/282).
+ - k8s - handle ValueError when namespace is not provided (https://github.com/ansible-collections/community.kubernetes/pull/330).
+ - respect the ``wait_timeout`` parameter in the ``k8s`` and ``k8s_info`` modules
+ when a resource does not exist (https://github.com/ansible-collections/community.kubernetes/issues/344).
+ minor_changes:
+ - Adjust the documentation to clarify the fact ``wait_condition.status`` is
+ a string.
+ - Adjust the name of parameters of ``helm`` and ``helm_info`` to match the documentation.
+ No playbook change required.
+ - The Helm modules (``helm``, ``helm_info``, ``helm_plugin``, ``helm_plugin_info``,
+ ``helm_plugin_repository``) accept the K8S environment variables like the
+ other modules of the collections.
+ - helm - add a ``skip_crds`` option to skip the installation of CRDs when installing
+ or upgrading a chart (https://github.com/ansible-collections/community.kubernetes/issues/296).
+ - helm - add optional support for helm diff (https://github.com/ansible-collections/community.kubernetes/issues/248).
+ - helm_template - add helm_template module to support template functionality
+ (https://github.com/ansible-collections/community.kubernetes/issues/367).
+ - k8s - add a ``delete_options`` parameter to control garbage collection behavior
+ when deleting a resource (https://github.com/ansible-collections/community.kubernetes/issues/253).
+ - k8s - add an example for downloading manifest file and applying (https://github.com/ansible-collections/community.kubernetes/issues/352).
+ - k8s - check if kubeconfig file is located on remote node or on Ansible Controller
+ (https://github.com/ansible-collections/community.kubernetes/issues/307).
+ - k8s - check if src file is located on remote node or on Ansible Controller
+ (https://github.com/ansible-collections/community.kubernetes/issues/307).
+ - k8s_exec - add a note about required permissions for the module (https://github.com/ansible-collections/community.kubernetes/issues/339).
+ - k8s_info - add information about api_version while returning facts (https://github.com/ansible-collections/community.kubernetes/pull/308).
+ - runtime.yml - update minimum Ansible version required for Kubernetes collection
+ (https://github.com/ansible-collections/community.kubernetes/issues/314).
+ fragments:
+ - 280_helm_status.yml
+ - 307_remote_src.yml
+ - 308_k8s_info.yml
+ - 310-wait_condition.status_is_a_str.yaml
+ - 314_version.yml
+ - 319-helm-honors-HELM_-environment-variables.yaml
+ - 324-adjust-helm-and-helm_info-parameters-names.yaml
+ - 332_helm_changed_flag_takes_values_in_consideration.yaml
+ - 334-delete-options.yaml
+ - 343-secret-check-mode.yaml
+ - 349-skip-crds.yaml
+ - 352-k8s.yml
+ - 355-helm-diff.yaml
+ - 357_helm_plugin.yml
+ - 360-k8s_info-wait-timeout.yaml
+ - 361-k8s_exec-permission-hint.yaml
+ - 362-helm-has_plugin-fix.yaml
+ - 368-helm_template.yaml
+ - handle_valueerror.yml
+ release_date: '2021-02-17'
+ 2.0.0:
+ changes:
+ breaking_changes:
+ - Drop python 2 support (https://github.com/ansible-collections/kubernetes.core/pull/86).
+ - helm_plugin - remove unused ``release_namespace`` parameter (https://github.com/ansible-collections/kubernetes.core/pull/85).
+ - helm_plugin_info - remove unused ``release_namespace`` parameter (https://github.com/ansible-collections/kubernetes.core/pull/85).
+ - k8s_cluster_info - returned apis as list to avoid being overwritten in case
+ of multiple version (https://github.com/ansible-collections/kubernetes.core/pull/41).
+ - k8s_facts - remove the deprecated alias from k8s_facts to k8s_info (https://github.com/ansible-collections/kubernetes.core/pull/125).
+ bugfixes:
+ - enable unit tests in CI (https://github.com/ansible-collections/community.kubernetes/pull/407).
+ - helm - Accept ``validate_certs`` with a ``context`` (https://github.com/ansible-collections/kubernetes.core/pull/74).
+ - helm - fix helm ignoring the kubeconfig context when passed through the ``context``
+ param or the ``K8S_AUTH_CONTEXT`` environment variable (https://github.com/ansible-collections/community.kubernetes/issues/385).
+ - helm - handle multiline output of ``helm plugin list`` command (https://github.com/ansible-collections/community.kubernetes/issues/399).
+ - k8s - fix merge_type option when set to json (https://github.com/ansible-collections/kubernetes.core/issues/54).
+ - k8s - lookup should return list even if single item is found (https://github.com/ansible-collections/kubernetes.core/issues/9).
+ - k8s inventory - remove extra trailing slashes from the hostname (https://github.com/ansible-collections/kubernetes.core/issues/52).
+ major_changes:
+ - k8s - deprecate merge_type=json. The JSON patch functionality has never worked
+ (https://github.com/ansible-collections/kubernetes.core/pull/99).
+ - k8s_json_patch - split JSON patch functionality out into a separate module
+ (https://github.com/ansible-collections/kubernetes.core/pull/99).
+ - replaces the openshift client with the official kubernetes client (https://github.com/ansible-collections/kubernetes.core/issues/34).
+ minor_changes:
+ - Add cache_file when DynamicClient is created (https://github.com/ansible-collections/kubernetes.core/pull/46).
+ - Add configmap and secret hash functionality (https://github.com/ansible-collections/kubernetes.core/pull/48).
+ - Add logic for cache file name generation (https://github.com/ansible-collections/kubernetes.core/pull/46).
+ - Replicate apply method in the DynamicClient (https://github.com/ansible-collections/kubernetes.core/pull/45).
+ - add ``proxy_headers`` option for authentication on k8s_xxx modules (https://github.com/ansible-collections/kubernetes.core/pull/58).
+ - add support for using tags when running molecule test suite (https://github.com/ansible-collections/kubernetes.core/pull/62).
+ - added documentation for ``kubernetes.core`` collection (https://github.com/ansible-collections/kubernetes.core/pull/50).
+ - common - removed ``KubernetesAnsibleModule``, use ``K8sAnsibleMixin`` instead
+ (https://github.com/ansible-collections/kubernetes.core/pull/70).
+ - helm - add example for complex values in ``helm`` module (https://github.com/ansible-collections/kubernetes.core/issues/109).
+ - k8s - Handle list of definition for option `template` (https://github.com/ansible-collections/kubernetes.core/pull/49).
+ - k8s - `continue_on_error` option added (whether to continue on creation/deletion
+ errors) (https://github.com/ansible-collections/kubernetes.core/pull/49).
+ - k8s - support ``patched`` value for ``state`` option. patched state is an
+ existing resource that has a given patch applied (https://github.com/ansible-collections/kubernetes.core/pull/90).
+ - k8s - wait for all pods to update when rolling out daemonset changes (https://github.com/ansible-collections/kubernetes.core/pull/102).
+ - k8s_scale - ability to scale multiple resource using ``label_selectors`` (https://github.com/ansible-collections/kubernetes.core/pull/114).
+ - k8s_scale - new parameter to determine whether to continue or not on error
+ when scaling multiple resources (https://github.com/ansible-collections/kubernetes.core/pull/114).
+ - kubeconfig - update ``kubeconfig`` file location in the documentation (https://github.com/ansible-collections/kubernetes.core/issues/53).
+ - remove old change log fragment files.
+ - remove the deprecated ``KubernetesRawModule`` class (https://github.com/ansible-collections/community.kubernetes/issues/232).
+ - replicate base resource for lists functionality (https://github.com/ansible-collections/kubernetes.core/pull/89).
+ fragments:
+ - 102-wait-updated-daemonset-pods.yaml
+ - 114-k8s_scale-add-label-selectors-and-continue-on-error.yaml
+ - 125-remove-k8s-facts-alias.yaml
+ - 379-remove-kubernetesrawmodule.yaml
+ - 387-fix-helm-ignoring-context.yaml
+ - 399-helm_multiline.yml
+ - 407-enable-unit-tests.yaml
+ - 41-fix-apis-being-overwritten-in-k8s_cluster_info.yaml
+ - 45-add-apply-method.yml
+ - 46-cachefile_dynamic_client.yml
+ - 48_hash-configmap-secret.yml
+ - 49-k8s-loop-flattening-and-continue_on_error.yaml
+ - 52_inventory.yml
+ - 53_kubeconfig_docs.yml
+ - 58-add-support-for-proxy_headers-on-authentication.yaml
+ - 62-molecule-tags.yaml
+ - 83-k8s-fix-merge_type-json.yaml
+ - 85_helm_plugin.yaml
+ - 86_drop_python2_support.yaml
+ - 89-replicate-base-resource.yaml
+ - 90-k8s-add-parameter-patch_only.yml
+ - 96-replace-openshift-client.yaml
+ - 99-json-patch-module.yaml
+ - 9_lookup_k8s.yml
+ - add_docs.yml
+ - helm_example.yml
+ - helm_validate_certs_not_exclusive.yaml
+ - remove_KubernetesAnsibleModule.yml
+ - remove_fragment.yml
+ modules:
+ - description: Apply JSON patch operations to existing objects
+ name: k8s_json_patch
+ namespace: ''
+ release_date: '2021-06-09'
+ 2.0.1:
+ changes:
+ bugfixes:
+ - inventory - add community.kubernetes to list of plugin choices in k8s inventory
+ (https://github.com/ansible-collections/kubernetes.core/pull/128).
+ fragments:
+ - 128-update-inventory-plugin-param.yaml
+ release_date: '2021-06-11'
+ 2.0.2:
+ changes:
+ bugfixes:
+ - Fix apply for k8s module when an array attribute from definition contains
+ empty dict (https://github.com/ansible-collections/kubernetes.core/issues/113).
+ - rename the apply function to fix broken imports in Ansible 2.9 (https://github.com/ansible-collections/kubernetes.core/pull/135).
+ fragments:
+ - 129-k8s-fix-apply-array-with-empty-dict.yml
+ - 135-rename-apply-function.yml
+ release_date: '2021-06-16'
+ 2.1.0:
+ changes:
+ minor_changes:
+ - remove cloud.common as default dependency (https://github.com/ansible-collections/kubernetes.core/pull/148).
+ - temporarily disable turbo mode (https://github.com/ansible-collections/kubernetes.core/pull/149).
+ fragments:
+ - 148-remove-cloud-common-dependency.yaml
+ - 149-disable-turbo-mode.yaml
+ release_date: '2021-06-23'
+ 2.1.1:
+ changes:
+ bugfixes:
+ - check auth params for existence, not whether they are true (https://github.com/ansible-collections/kubernetes.core/pull/151).
+ fragments:
+ - 151-check-auth-params-for-existence.yaml
+ release_date: '2021-06-24'
+ 2.2.0:
+ changes:
+ bugfixes:
+ - common - import k8sdynamicclient directly to workaround Ansible upstream bug
+ (https://github.com/ansible-collections/kubernetes.core/issues/162).
+ - connection plugin - add arguments information into censored command (https://github.com/ansible-collections/kubernetes.core/pull/196).
+ - fix resource cache not being used (https://github.com/ansible-collections/kubernetes.core/pull/228).
+ - k8s - Fixes a bug where diff was always returned when using apply or modifying
+ an existing object, even when diff=no was specified. The module no longer
+ returns diff unless requested and will now honor diff=no (https://github.com/ansible-collections/kubernetes.core/pull/146).
+ - k8s_cp - fix k8s_cp uploading when target container's WORKDIR is not '/' (https://github.com/ansible-collections/kubernetes.core/issues/222).
+ - k8s_exec - add missing deprecation notice to return_code for k8s_exec (https://github.com/ansible-collections/kubernetes.core/pull/233).
+ - k8s_exec - fix k8s_exec returning rc attribute, to follow ansible's common
+ return values (https://github.com/ansible-collections/kubernetes.core/pull/230).
+ - lookup - recommend query instead of lookup (https://github.com/ansible-collections/kubernetes.core/issues/147).
+ - support the ``template`` param in all collections depending on kubernetes.core
+ (https://github.com/ansible-collections/kubernetes.core/pull/154).
+ minor_changes:
+ - add support for in-memory kubeconfig in addition to file for k8s modules.
+ (https://github.com/ansible-collections/kubernetes.core/pull/212).
+ - helm - add support for history_max cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/164).
+ - k8s - add support for label_selectors options (https://github.com/ansible-collections/kubernetes.core/issues/43).
+ - k8s - add support for waiting on statefulsets (https://github.com/ansible-collections/kubernetes.core/pull/195).
+ - k8s_log - Add since-seconds parameter to the k8s_log module (https://github.com/ansible-collections/kubernetes.core/pull/142).
+ - new lookup plugin to support kubernetes kustomize feature. (https://github.com/ansible-collections/kubernetes.core/issues/39).
+ - re-enable turbo mode for collection. The default is initially set to off (https://github.com/ansible-collections/kubernetes.core/pull/169).
+ fragments:
+ - 142-add-sinceseconds-param-for-logs.yaml
+ - 146-k8s-add-support-diff-mode.yml
+ - 147_lookup.yml
+ - 154-template-param-support.yaml
+ - 158-k8s-add-support-label_selectors.yml
+ - 162_import_error.yml
+ - 164-add-history-max.yaml
+ - 169-reenable-turbo-mode.yaml
+ - 195-k8s-add-wait-statefulsets.yml
+ - 196_kubectl.yaml
+ - 212-in-memory-kubeconfig.yml
+ - 223-add-deprecation-notice.yaml
+ - 223-k8s-cp-uploading.yaml
+ - 225-kustomize-lookup-plugin.yml
+ - 228-fix-resource-cache.yml
+ - 230-k8sexec-has-new-returnvalue.yml
+ modules:
+ - description: Copy files and directories to and from pod.
+ name: k8s_cp
+ namespace: ''
+ - description: Drain, Cordon, or Uncordon node in k8s cluster
+ name: k8s_drain
+ namespace: ''
+ plugins:
+ lookup:
+ - description: Build a set of kubernetes resources using a 'kustomization.yaml'
+ file.
+ name: kustomize
+ namespace: null
+ release_date: '2021-09-15'
+ 2.3.0:
+ changes:
+ bugfixes:
+ - Various modules and plugins - use vendored version of ``distutils.version``
+ instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
+ - common - Ensure the label_selectors parameter of _wait_for method is optional.
+ - helm_template - evaluate release_values after values_files, insuring highest
+ precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
+ - import exception from ``kubernetes.client.rest``.
+ - k8s_drain - fix error caused by accessing an undefined variable when pods
+ have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
+ - k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
+ - k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
+ - module_utils.common - change default opening mode to read-bytes to avoid bad
+ interpretation of non ascii characters and strings, often present in 3rd party
+ manifests.
+ - remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
+ - use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
+ minor_changes:
+ - add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
+ - fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
+ - helm - add support for timeout cli parameter to allow setting Helm timeout
+ independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
+ - helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
+ - helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
+ - helm - when ansible is executed in check mode, return the diff between what's
+ deployed and what will be deployed.
+ - helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
+ - helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
+ - helm_plugin - Add support for helm plugin update using state=update.
+ - helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
+ - helm_repository - added support for ``host``, ``api_key``, ``validate_certs``,
+ and ``ca_cert``.
+ - helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+ - k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
+ - k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
+ - k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
+ - k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
+ - k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
+ - k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options``
+ to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
+ - k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
+ - k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
+ - k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
+ - kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
+ fragments:
+ - 0-copy_ignore_txt.yml
+ - 226-add-version-parameter-to-helm_plugin.yml
+ - 231-helm-add-timeout-parameter.yaml
+ - 238-helm-add-support-for-helm-uninstall-wait.yaml
+ - 238-k8s-add-support-for-generate_name.yml
+ - 245-add-dry-run.yaml
+ - 250-k8s-add-support-for-impersonation.yaml
+ - 253-dont-wait-on-list-resources.yaml
+ - 255-k8s_scale-k8s_rollback-add-support-for-check_mode.yml
+ - 260-k8s-add-support-for-server_side_apply.yml
+ - 272-k8s-add-support-no_proxy.yaml
+ - 282-helm-repository-add-pass-credentials.yaml
+ - 290-returns-diff-in-check-mode.yaml
+ - 295-fix-k8s-drain-variable-declaration.yaml
+ - 298-remove-binary-file.yaml
+ - 308-fix-for-common-non-ascii-characters-in-resources.yaml
+ - 313-helm-template-add-support-for-show-only-and-release-namespace.yml
+ - 321-kubectl_sleep.yml
+ - 322-Add-delete_emptydir_data-to-drain-delete_options.yaml
+ - 335-k8s-lookup-add-support-for-turbo-mode.yml
+ - 347-routing.yml
+ - 348-helm_template-fix-precedence-of-release-values-over-values-files.yaml
+ - 358-k8s_exec.yml
+ - 364-use-resource-prefix.yaml
+ - 377-helm-info-state.yml
+ - 389-helm-add-support-chart_repo_url-on-helm_diff.yml
+ - 391-fix-statefulset-wait.yaml
+ - _wait_for_label_selector_optional.yaml
+ - disutils.version.yml
+ - exception.yml
+ - helm_repository.yml
+ modules:
+ - description: Taint a node in a Kubernetes/OpenShift cluster
+ name: k8s_taint
+ namespace: ''
+ release_date: '2022-03-11'
+ 2.3.1:
+ changes:
+ bugfixes:
+ - Catch expectation raised when the process is waiting for resources (https://github.com/ansible-collections/kubernetes.core/issues/407).
+ - Remove `omit` placeholder when defining resource using template parameter
+ (https://github.com/ansible-collections/kubernetes.core/issues/431).
+ - k8s - fix the issue when trying to delete resources using label_selectors
+ options (https://github.com/ansible-collections/kubernetes.core/issues/433).
+ - k8s_cp - fix issue when using parameter local_path with file on managed node.
+ (https://github.com/ansible-collections/kubernetes.core/issues/421).
+ - k8s_drain - fix error occurring when trying to drain node with disable_eviction
+ set to yes (https://github.com/ansible-collections/kubernetes.core/issues/416).
+ fragments:
+ - 408-fix-wait-on-exception.yml
+ - 417-fix-k8s-drain-delete-options.yaml
+ - 422-k8s_cp-fix-issue-when-issue-local_path.yaml
+ - 432-fix-issue-when-using-template-parameter.yaml
+ - 434-fix-k8s-delete-using-label_selector.yaml
+ release_date: '2022-05-02'
+ 2.4.0:
+ changes:
+ bugfixes:
+ - Fix dry_run logic - Pass the value dry_run=All instead of dry_run=True to
+ the client, add conditional check on kubernetes client version as this feature
+ is supported only for kubernetes >= 18.20.0 (https://github.com/ansible-collections/kubernetes.core/pull/561).
+ - Fix kubeconfig parameter when multiple config files are provided (https://github.com/ansible-collections/kubernetes.core/issues/435).
+ - Helm - Fix issue with alternative kubeconfig provided with validate_certs=False
+ (https://github.com/ansible-collections/kubernetes.core/issues/538).
+ - Various modules and plugins - use vendored version of ``distutils.version``
+ instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/kubernetes.core/pull/314).
+ - add missing documentation for filter plugin kubernetes.core.k8s_config_resource_name
+ (https://github.com/ansible-collections/kubernetes.core/issues/558).
+ - common - Ensure the label_selectors parameter of _wait_for method is optional.
+ - common - handle ``aliases`` passed from inventory and lookup plugins.
+ - helm_template - evaluate release_values after values_files, insuring highest
+ precedence (now same behavior as in helm module). (https://github.com/ansible-collections/kubernetes.core/pull/348)
+ - import exception from ``kubernetes.client.rest``.
+ - k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547).
+ - k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548).
+ - k8s_cp - add support for check_mode (https://github.com/ansible-collections/kubernetes.core/issues/380).
+ - k8s_drain - fix error caused by accessing an undefined variable when pods
+ have local storage (https://github.com/ansible-collections/kubernetes.core/issues/292).
+ - k8s_info - don't wait on empty List resources (https://github.com/ansible-collections/kubernetes.core/pull/253).
+ - k8s_info - fix issue when module returns successful true after the resource
+ cache has been established during periods where communication to the api-server
+ is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508).
+ - k8s_log - Fix module traceback when no resource found (https://github.com/ansible-collections/kubernetes.core/issues/479).
+ - k8s_log - fix exception raised when the name is not provided for resources
+ requiring. (https://github.com/ansible-collections/kubernetes.core/issues/514)
+ - k8s_scale - fix waiting on statefulset when scaled down to 0 replicas (https://github.com/ansible-collections/kubernetes.core/issues/203).
+ - module_utils.common - change default opening mode to read-bytes to avoid bad
+ interpretation of non ascii characters and strings, often present in 3rd party
+ manifests.
+ - module_utils/k8s/client.py - fix issue when trying to authenticate with host,
+ client_cert and client_key parameters only.
+ - remove binary file from k8s_cp test suite (https://github.com/ansible-collections/kubernetes.core/pull/298).
+ - use resource prefix when finding resource and apiVersion is v1 (https://github.com/ansible-collections/kubernetes.core/issues/351).
+ major_changes:
+ - refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481).
+ minor_changes:
+ - Adjust k8s_user_impersonation tests to be compatible with Kubernetes 1.24
+ (https://github.com/ansible-collections/kubernetes.core/pull/520).
+ - add support for dry run with kubernetes client version >=18.20 (https://github.com/ansible-collections/kubernetes.core/pull/245).
+ - added ignore.txt for Ansible 2.14 devel branch.
+ - fixed module_defaults by removing routing hacks from runtime.yml (https://github.com/ansible-collections/kubernetes.core/pull/347).
+ - helm - add support for -set-file, -set-json, -set and -set-string options
+ when running helm install (https://github.com/ansible-collections/kubernetes.core/issues/533).
+ - helm - add support for helm dependency update (https://github.com/ansible-collections/kubernetes.core/pull/208).
+ - helm - add support for post-renderer flag (https://github.com/ansible-collections/kubernetes.core/issues/30).
+ - helm - add support for timeout cli parameter to allow setting Helm timeout
+ independent of wait (https://github.com/ansible-collections/kubernetes.core/issues/67).
+ - helm - add support for wait parameter for helm uninstall command. (https://github.com/ansible-collections/kubernetes/core/issues/33).
+ - helm - support repo location for helm diff (https://github.com/ansible-collections/kubernetes.core/issues/174).
+ - helm - when ansible is executed in check mode, return the diff between what's
+ deployed and what will be deployed.
+ - helm, helm_plugin, helm_info, helm_plugin_info, kubectl - add support for
+ in-memory kubeconfig. (https://github.com/ansible-collections/kubernetes.core/issues/492).
+ - helm_info - add hooks, notes and manifest as part of returned information
+ (https://github.com/ansible-collections/kubernetes.core/pull/546).
+ - helm_info - add release state as a module argument (https://github.com/ansible-collections/kubernetes.core/issues/377).
+ - helm_info - added possibility to get all values by adding get_all_values parameter
+ (https://github.com/ansible-collections/kubernetes.core/pull/531).
+ - helm_plugin - Add plugin_version parameter to the helm_plugin module (https://github.com/ansible-collections/kubernetes.core/issues/157).
+ - helm_plugin - Add support for helm plugin update using state=update.
+ - helm_repository - Ability to replace (overwrite) the repo if it already exists
+ by forcing (https://github.com/ansible-collections/kubernetes.core/issues/491).
+ - helm_repository - add support for pass-credentials cli parameter (https://github.com/ansible-collections/kubernetes.core/pull/282).
+ - helm_repository - added support for ``host``, ``api_key``, ``validate_certs``,
+ and ``ca_cert``.
+ - helm_repository - mark `pass_credentials` as no_log=True to silence false
+ warning (https://github.com/ansible-collections/kubernetes.core/issues/412).
+ - helm_template - add name (NAME of release) and disable_hook as optional module
+ arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+ - helm_template - add show_only and release_namespace as module arguments (https://github.com/ansible-collections/kubernetes.core/issues/313).
+ - helm_template - add support for -set-file, -set-json, -set and -set-string
+ options when running helm template (https://github.com/ansible-collections/kubernetes.core/pull/546).
+ - k8s - add no_proxy support to k8s* (https://github.com/ansible-collections/kubernetes.core/pull/272).
+ - k8s - add support for server_side_apply. (https://github.com/ansible-collections/kubernetes.core/issues/87).
+ - k8s - add support for user impersonation. (https://github.com/ansible-collections/kubernetes/core/issues/40).
+ - k8s - allow resource definition using metadata.generateName (https://github.com/ansible-collections/kubernetes.core/issues/35).
+ - k8s lookup plugin - Enable turbo mode via environment variable (https://github.com/ansible-collections/kubernetes.core/issues/291).
+ - k8s, k8s_scale, k8s_service - add support for resource definition as manifest
+ via. (https://github.com/ansible-collections/kubernetes.core/issues/451).
+ - k8s_cp - remove dependency with 'find' executable on remote pod when state=from_pod
+ (https://github.com/ansible-collections/kubernetes.core/issues/486).
+ - k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options``
+ to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322).
+ - k8s_exec - select first container from the pod if none specified (https://github.com/ansible-collections/kubernetes.core/issues/358).
+ - k8s_exec - update deprecation warning for `return_code` (https://github.com/ansible-collections/kubernetes.core/issues/417).
+ - k8s_json_patch - minor typo fix in the example section (https://github.com/ansible-collections/kubernetes.core/issues/411).
+ - k8s_log - add the ``all_containers`` for retrieving all containers' logs in
+ the pod(s).
+ - k8s_log - added the `previous` parameter for retrieving the previously terminated
+ pod logs (https://github.com/ansible-collections/kubernetes.core/issues/437).
+ - k8s_log - added the `tail_lines` parameter to limit the number of lines to
+ be retrieved from the end of the logs (https://github.com/ansible-collections/kubernetes.core/issues/488).
+ - k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
+ - k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
+ - kubectl - wait for dd command to complete before proceeding (https://github.com/ansible-collections/kubernetes.core/pull/321).
+ - kubectl.py - replace distutils.spawn.find_executable with shutil.which in
+ the kubectl connection plugin (https://github.com/ansible-collections/kubernetes.core/pull/456).
+ fragments:
+ - 0-copy_ignore_txt.yml
+ - 208-add-dependency-update.yaml
+ - 226-add-version-parameter-to-helm_plugin.yml
+ - 231-helm-add-timeout-parameter.yaml
+ - 238-helm-add-support-for-helm-uninstall-wait.yaml
+ - 238-k8s-add-support-for-generate_name.yml
+ - 245-add-dry-run.yaml
+ - 250-k8s-add-support-for-impersonation.yaml
+ - 253-dont-wait-on-list-resources.yaml
+ - 255-k8s_scale-k8s_rollback-add-support-for-check_mode.yml
+ - 260-k8s-add-support-for-server_side_apply.yml
+ - 272-k8s-add-support-no_proxy.yaml
+ - 282-helm-repository-add-pass-credentials.yaml
+ - 290-returns-diff-in-check-mode.yaml
+ - 295-fix-k8s-drain-variable-declaration.yaml
+ - 298-remove-binary-file.yaml
+ - 30-helm-add-post-renderer-support.yml
+ - 308-fix-for-common-non-ascii-characters-in-resources.yaml
+ - 313-helm-template-add-support-for-name-and-disablehook.yml
+ - 313-helm-template-add-support-for-show-only-and-release-namespace.yml
+ - 321-kubectl_sleep.yml
+ - 322-Add-delete_emptydir_data-to-drain-delete_options.yaml
+ - 335-k8s-lookup-add-support-for-turbo-mode.yml
+ - 347-routing.yml
+ - 348-helm_template-fix-precedence-of-release-values-over-values-files.yaml
+ - 358-k8s_exec.yml
+ - 364-use-resource-prefix.yaml
+ - 377-helm-info-state.yml
+ - 389-helm-add-support-chart_repo_url-on-helm_diff.yml
+ - 391-fix-statefulset-wait.yaml
+ - 411_k8s_json_patch.yml
+ - 412_pass_creds.yml
+ - 417_deprecation.yml
+ - 428-fix-kubeconfig-parameter-with-multiple-config-files.yaml
+ - 437-k8s-add-support-for-previous-logs.yaml
+ - 456-replace-distutils.yml
+ - 478-add-support-for-manifest-url.yaml
+ - 481-refactor-common.yml
+ - 488-add-support-for-tail-logs.yaml
+ - 493-k8s_log-fix-module-when-pod-does-exist.yaml
+ - 497-helm-add-support-for-in-memory-kubeconfig.yml
+ - 498-k8s-honor-aliases.yaml
+ - 505-add-from-yaml-all-example.yml
+ - 509-helm-repo-add-force_update-argument.yaml
+ - 512-k8s_cp-add-support-for-check_mode-update-command-for-listing-files-into-pod.yaml
+ - 515-update-sanity-for-2-15.yml
+ - 522-fix-helm-tests.yml
+ - 523-helm_info-get-all-values.yaml
+ - 528-k8s_log-support-all_containers-options.yml
+ - 532-k8s_crd-fix-integration-test.yml
+ - 546-helm-install-add-support-for-set-options.yaml
+ - 549-fix-server-side-apply.yaml
+ - 552-k8s_cp-fix-issue-when-copying-item-with-space-in-its-name.yml
+ - 561-fix-dry-run.yml
+ - 562-helm-fix-issue-when-alternative-kubeconfig-is-provided.yaml
+ - 571-k8s_info-fix-issue-with-api-server.yaml
+ - _wait_for_label_selector_optional.yaml
+ - disutils.version.yml
+ - exception.yml
+ - fix-ci-unit-tests.yaml
+ - helm_repository.yml
+ - ignore_2.14.yml
+ - k8s_config_resource_name-add-missing-documentation.yml
+ - k8s_rollback_reduce_tmeouts.yaml
+ - k8s_user_impersonation_k8s_1_24.yaml
+ - minor-tests-duration.yaml
+ modules:
+ - description: download a chart from a repository and (optionally) unpack it in
+ local directory.
+ name: helm_pull
+ namespace: ''
+ release_date: '2023-01-24'
diff --git a/ansible_collections/kubernetes/core/changelogs/config.yaml b/ansible_collections/kubernetes/core/changelogs/config.yaml
new file mode 100644
index 00000000..69554b84
--- /dev/null
+++ b/ansible_collections/kubernetes/core/changelogs/config.yaml
@@ -0,0 +1,30 @@
+---
+changelog_filename_template: ../CHANGELOG.rst
+changelog_filename_version_depth: 0
+changes_file: changelog.yaml
+changes_format: combined
+keep_fragments: false
+mention_ancestor: true
+new_plugins_after_name: removed_features
+notesdir: fragments
+prelude_section_name: release_summary
+prelude_section_title: Release Summary
+sections:
+- - major_changes
+ - Major Changes
+- - minor_changes
+ - Minor Changes
+- - breaking_changes
+ - Breaking Changes / Porting Guide
+- - deprecated_features
+ - Deprecated Features
+- - removed_features
+ - Removed Features (previously deprecated)
+- - security_fixes
+ - Security Fixes
+- - bugfixes
+ - Bugfixes
+- - known_issues
+ - Known Issues
+title: Kubernetes Collection
+trivial_section_name: trivial
diff --git a/ansible_collections/kubernetes/core/codecov.yml b/ansible_collections/kubernetes/core/codecov.yml
new file mode 100644
index 00000000..71e957c6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/codecov.yml
@@ -0,0 +1,8 @@
+---
+coverage:
+ precision: 2
+ round: down
+ range: "70...100"
+ status:
+ project:
+ default: false
diff --git a/ansible_collections/kubernetes/core/docs/ansible_turbo_mode.rst b/ansible_collections/kubernetes/core/docs/ansible_turbo_mode.rst
new file mode 100644
index 00000000..6e0c990e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/ansible_turbo_mode.rst
@@ -0,0 +1,147 @@
+.. _ansible_turbo_mode:
+
+
+******************
+Ansible Turbo mode
+******************
+
+Following document provides overview of Ansible Turbo mode in ``kubernetes.core`` collection.
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- A brief introduction about Ansible Turbo mode in ``kuberentes.core`` collection.
+- Ansible Turbo mode is an optional performance optimization. It can be enabled by installing the cloud.common collection and setting the ``ENABLE_TURBO_MODE`` environment variable.
+
+Requirements
+------------
+
+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
+------------
+
+You can install ``cloud.common`` collection using following command::
+
+ # ansible-galaxy collection install cloud.common
+
+
+Current situation without Ansible Turbo mode
+============================================
+
+The traditional execution flow of an Ansible module includes the following steps:
+
+- Upload of a ZIP archive with the module and its dependencies
+- Execution of the module
+- Ansible collects the results once the script is finished
+
+These steps happen for each task of a playbook, and on every host.
+
+Most of the time, the execution of a module is fast enough for
+the user. However, sometime the module requires significant amount of time,
+just to initialize itself. This is a common situation with the API based modules.
+
+A classic initialization involves the following steps:
+
+- Load a Python library to access the remote resource (via SDK)
+- Open a client
+ - Load a bunch of Python modules.
+ - Request a new TCP connection.
+ - Create a session.
+ - Authenticate the client.
+
+All these steps are time consuming and the same operations will be running again and again.
+
+For instance, here:
+
+- ``import openstack``: takes 0.569s
+- ``client = openstack.connect()``: takes 0.065s
+- ``client.authorize()``: takes 1.360s,
+
+These numbers are from test running against VexxHost public cloud.
+
+In this case, it's a 2s-ish overhead per task. If the playbook
+comes with 10 tasks, the execution time cannot go below 20s.
+
+How Ansible Turbo Module improve the situation
+==============================================
+
+``AnsibleTurboModule`` is actually a class that inherites from
+the standard ``AnsibleModule`` class that your modules probably
+already use.
+The big difference is that when a module starts, it also spawns
+a little Python daemon. If a daemon already exists, it will just
+reuse it.
+All the module logic is run inside this Python daemon. This means:
+
+- Python modules are actually loaded one time
+- Ansible module can reuse an existing authenticated session.
+
+The background service
+======================
+
+The daemon kills itself after 15s, and communication are done
+through an Unix socket.
+It runs in one single process and uses ``asyncio`` internally.
+Consequently you can use the ``async`` keyword in your Ansible module.
+This will be handy if you interact with a lot of remote systems
+at the same time.
+
+Security impact
+===============
+
+``ansible_module.turbo`` open an Unix socket to interact with the background service.
+We use this service to open the connection toward the different target systems.
+
+This is similar to what SSH does with the sockets.
+
+Keep in mind that:
+
+- All the modules can access the same cache. Soon an isolation will be done at the collection level (https://github.com/ansible-collections/cloud.common/pull/17)
+- A task can load a different version of a library and impact the next tasks.
+- If the same user runs two ``ansible-playbook`` at the same time, they will have access to the same cache.
+
+When a module stores a session in a cache, it's a good idea to use a hash of the authentication information to identify the session.
+
+Error management
+================
+
+``ansible_module.turbo`` uses exceptions to communicate a result back to the module.
+
+- ``EmbeddedModuleFailure`` is raised when ``json_fail()`` is called.
+- ``EmbeddedModuleSuccess`` is raised in case of success and returns the result to the origin module process.
+
+These exceptions are defined in ``ansible_collections.cloud.common.plugins.module_utils.turbo.exceptions``.
+You can raise ``EmbeddedModuleFailure`` exception yourself, for instance from a module in ``module_utils``.
+
+.. note:: Be careful with the ``except Exception:`` blocks.
+ Not only they are bad practice, but also may interface with this
+ mechanism.
+
+
+Troubleshooting
+===============
+
+You may want to manually start the server. This can be done with the following command:
+
+.. code-block:: shell
+
+ PYTHONPATH=$HOME/.ansible/collections python -m ansible_collections.cloud.common.plugins.module_utils.turbo.server --socket-path $HOME/.ansible/tmp/turbo_mode.kubernetes.core.socket
+
+You can use the ``--help`` argument to get a list of the optional parameters.
diff --git a/ansible_collections/kubernetes/core/docs/docsite/extra-docs.yml b/ansible_collections/kubernetes/core/docs/docsite/extra-docs.yml
new file mode 100644
index 00000000..969d8d86
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/extra-docs.yml
@@ -0,0 +1,5 @@
+---
+sections:
+ - title: Scenario Guide
+ toctree:
+ - scenario_guide
diff --git a/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst
new file mode 100644
index 00000000..e5bcfb8d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_intro.rst
@@ -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>`_.
diff --git a/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst
new file mode 100644
index 00000000..e7f4da9c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_inventory.rst
@@ -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
diff --git a/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst
new file mode 100644
index 00000000..df5eb57d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/k8s_scenarios.rst
@@ -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 \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst
new file mode 100644
index 00000000..90029e77
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/rst/kubernetes_scenarios/scenario_k8s_object.rst
@@ -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
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "utilitypod-1"
+ namespace: default
+ labels:
+ app: galaxy
+ spec:
+ containers:
+ - name: utilitypod
+ image: busybox
+
+Since Ansible utilizes the Kubernetes API to perform actions, in this use case we will be connecting directly to the Kubernetes cluster.
+
+To begin, there are a few bits of information we will need. Here you are using Kubeconfig which is pre-configured in your machine. The Kubeconfig is generally located at ``~/.kube/config``. It is highly recommended to store sensitive information such as password, user certificates in a more secure fashion using :ref:`ansible-vault` or using `Ansible Tower credentials <https://docs.ansible.com/ansible-tower/latest/html/userguide/credentials.html>`_.
+
+Now you need to supply the information about the Pod which will be created. Using ``definition`` parameter of the ``kubernetes.core.k8s`` module, you specify `PodTemplate <https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates>`_. This PodTemplate is identical to what you provide to the ``kubectl`` command.
+
+What to expect
+--------------
+
+- You will see a bit of JSON output after this playbook completes. This output shows various parameters that are returned from the module and from cluster about the newly created Pod.
+
+.. code:: json
+
+ {
+ "changed": true,
+ "method": "create",
+ "result": {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "creationTimestamp": "2020-10-03T15:36:25Z",
+ "labels": {
+ "app": "galaxy"
+ },
+ "name": "utilitypod-1",
+ "namespace": "default",
+ "resourceVersion": "4511073",
+ "selfLink": "/api/v1/namespaces/default/pods/utilitypod-1",
+ "uid": "c7dec819-09df-4efd-9d78-67cf010b4f4e"
+ },
+ "spec": {
+ "containers": [{
+ "image": "busybox",
+ "imagePullPolicy": "Always",
+ "name": "utilitypod",
+ "resources": {},
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "volumeMounts": [{
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+ "name": "default-token-6j842",
+ "readOnly": true
+ }]
+ }],
+ "dnsPolicy": "ClusterFirst",
+ "enableServiceLinks": true,
+ "priority": 0,
+ "restartPolicy": "Always",
+ "schedulerName": "default-scheduler",
+ "securityContext": {},
+ "serviceAccount": "default",
+ "serviceAccountName": "default",
+ "terminationGracePeriodSeconds": 30,
+ "tolerations": [{
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/not-ready",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ },
+ {
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/unreachable",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ }
+ ],
+ "volumes": [{
+ "name": "default-token-6j842",
+ "secret": {
+ "defaultMode": 420,
+ "secretName": "default-token-6j842"
+ }
+ }]
+ },
+ "status": {
+ "phase": "Pending",
+ "qosClass": "BestEffort"
+ }
+ }
+ }
+
+- In the above example, 'changed' is ``True`` which notifies that the Pod creation started on the given cluster. This can take some time depending on your environment.
+
+
+Troubleshooting
+---------------
+
+Things to inspect
+
+- Check if the values provided for username and password are correct
+- Check if the Kubeconfig is populated with correct values
+
+.. seealso::
+
+ `Kubernetes Python client <https://github.com/kubernetes-client/python>`_
+ The GitHub Page of Kubernetes Python client
+ `Kubernetes Python client - Issue Tracker <https://github.com/kubernetes-client/python/issues>`_
+ The issue tracker for Kubernetes Python client
+ `Kubectl installation <https://kubernetes.io/docs/tasks/tools/install-kubectl/>`_
+ Installation guide for installing Kubectl
+ :ref:`working_with_playbooks`
+ An introduction to playbooks
+ :ref:`playbooks_vault`
+ Using Vault in playbooks
diff --git a/ansible_collections/kubernetes/core/docs/docsite/rst/scenario_guide.rst b/ansible_collections/kubernetes/core/docs/docsite/rst/scenario_guide.rst
new file mode 100644
index 00000000..c0fbaecc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/docsite/rst/scenario_guide.rst
@@ -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
+
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_info_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_info_module.rst
new file mode 100644
index 00000000..f74546b2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_info_module.rst
@@ -0,0 +1,381 @@
+.. _kubernetes.core.helm_info_module:
+
+
+*************************
+kubernetes.core.helm_info
+*************************
+
+**Get information from Helm package deployed inside the cluster**
+
+
+Version added: 0.11.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Get information (values, states, ...) from Helm package deployed inside the cluster.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm (https://github.com/helm/helm/releases)
+- yaml (https://pypi.org/project/PyYAML/)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via <code>K8S_AUTH_API_KEY</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via <code>K8S_AUTH_SSL_CA_CERT</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify which kubeconfig context to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_CONTEXT</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via <code>K8S_AUTH_HOST</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify kubeconfig path to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Release name to manage.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: name</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Kubernetes namespace where the chart should be installed.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: namespace</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via <code>K8S_AUTH_VERIFY_SSL</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Deploy latest version of Grafana chart inside monitoring namespace
+ kubernetes.core.helm_info:
+ name: test
+ release_namespace: monitoring
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>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>only when release exists</td>
+ <td>
+ <div>A dictionary of status output</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>app_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>always</td>
+ <td>
+ <div>Version of app deployed</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>chart</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Chart name and chart version</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Name of the release</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Namespace where the release is deployed</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>revision</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Number of time where the release has been updated</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">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Status of release (can be DEPLOYED, FAILED, ...)</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>updated</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>The Date of last update</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>values</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Dict of Values used to deploy</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Lucas Boisserie (@LucasBoisserie)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_module.rst
new file mode 100644
index 00000000..77b2c27b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_module.rst
@@ -0,0 +1,877 @@
+.. _kubernetes.core.helm_module:
+
+
+********************
+kubernetes.core.helm
+********************
+
+**Manages Kubernetes packages with the Helm package manager**
+
+
+Version added: 0.11.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Install, upgrade, delete packages with the Helm package manager.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm (https://github.com/helm/helm/releases)
+- yaml (https://pypi.org/project/PyYAML/)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via <code>K8S_AUTH_API_KEY</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>atomic</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>If set, the installation process deletes the installation on failure.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via <code>K8S_AUTH_SSL_CA_CERT</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ref</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>chart_reference on chart repository.</div>
+ <div>path to a packaged chart.</div>
+ <div>path to an unpacked chart directory.</div>
+ <div>absolute URL.</div>
+ <div>Required when <em>release_state</em> is set to <code>present</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_repo_url</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository URL where to locate the requested chart.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart version to install. If this is not specified, the latest version is installed.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify which kubeconfig context to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_CONTEXT</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>create_namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 0.11.1</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>Create the release namespace if not present.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>dependency_update</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Run standelone <code>helm dependency update CHART</code> before the operation.</div>
+ <div>Run inline <code>--dependency-update</code> with <code>helm install</code> command. This feature is not supported yet with the <code>helm upgrade</code> command.</div>
+ <div>So we should consider to use <em>dependency_update</em> options with <em>replace</em> option enabled when specifying <em>chart_repo_url</em>.</div>
+ <div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
+ <div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable_hook</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </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>Helm option to disable hook on install/upgrade/delete.</div>
+ </td>
+ </tr>
+ <tr>
+ <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>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>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via <code>K8S_AUTH_HOST</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify kubeconfig path to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>purge</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><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Remove the release from the store and make its name free for later use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Release name to manage.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: name</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Kubernetes namespace where the chart should be installed.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: namespace</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ <li>absent</li>
+ </ul>
+ </td>
+ <td>
+ <div>Desirated state of release.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: state</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_values</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">{}</div>
+ </td>
+ <td>
+ <div>Value to pass to chart.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: values</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>replace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.11.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Reuse the given name, only if that name is a deleted release which remains in the history.</div>
+ <div>This is unsafe in production environment.</div>
+ <div>mutually exclusive with with <code>history_max</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>skip_crds</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Skip custom resource definitions when installing or upgrading.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>timeout</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>A Go duration (described here <em>https://pkg.go.dev/time#ParseDuration</em>) value to wait for Kubernetes commands to complete. This defaults to 5m0s.</div>
+ <div>similar to <code>wait_timeout</code> but does not required <code>wait</code> to be activated.</div>
+ <div>Mutually exclusive with <code>wait_timeout</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>update_repo_cache</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <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>Run <code>helm repo update</code> before the operation. Can be run as part of the package installation or as a separate step (see Examples).</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via <code>K8S_AUTH_VERIFY_SSL</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>values_files</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 1.1.0</div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">[]</div>
+ </td>
+ <td>
+ <div>Value files to pass to chart.</div>
+ <div>Paths will be read from the target host&#x27;s filesystem, not the host running ansible.</div>
+ <div>values_files option is evaluated before values option if both are used.</div>
+ <div>Paths are evaluated in the order the paths are specified.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait</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>When <em>release_state</em> is set to <code>present</code>, wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.</div>
+ <div>When <em>release_state</em> is set to <code>absent</code>, will wait until all the resources are deleted before returning. It will wait for as long as <em>wait_timeout</em>. This feature requires helm&gt;=3.7.0. Added in version 2.3.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <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">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).</div>
+ <div>The use of <em>wait_timeout</em> to wait for kubernetes commands to complete has been deprecated and will be removed after 2022-12-01.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/prometheus
+ release_namespace: monitoring
+ create_namespace: true
+
+ # From repository
+ - name: Add stable chart repo
+ kubernetes.core.helm_repository:
+ name: stable
+ repo_url: "https://kubernetes.github.io/ingress-nginx"
+
+ - name: Deploy latest version of Grafana chart inside monitoring namespace with values
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ release_namespace: monitoring
+ values:
+ replicas: 2
+
+ - name: Deploy Grafana chart on 5.0.12 with values loaded from template
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ chart_version: 5.0.12
+ values: "{{ lookup('template', 'somefile.yaml') | from_yaml }}"
+
+ - name: Deploy Grafana chart using values files on target
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ release_namespace: monitoring
+ values_files:
+ - /path/to/values.yaml
+
+ - name: Remove test release and waiting suppression ending
+ kubernetes.core.helm:
+ name: test
+ state: absent
+ wait: true
+
+ - name: Separately update the repository cache
+ kubernetes.core.helm:
+ name: dummy
+ namespace: kube-system
+ state: absent
+ update_repo_cache: true
+
+ # From git
+ - name: Git clone stable repo on HEAD
+ ansible.builtin.git:
+ repo: "http://github.com/helm/charts.git"
+ dest: /tmp/helm_repo
+
+ - name: Deploy Grafana chart from local path
+ kubernetes.core.helm:
+ name: test
+ chart_ref: /tmp/helm_repo/stable/grafana
+ release_namespace: monitoring
+
+ # From url
+ - name: Deploy Grafana chart on 5.6.0 from url
+ kubernetes.core.helm:
+ name: test
+ chart_ref: "https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz"
+ release_namespace: monitoring
+
+ # Using complex Values
+ - name: Deploy new-relic client chart
+ kubernetes.core.helm:
+ name: newrelic-bundle
+ chart_ref: newrelic/nri-bundle
+ release_namespace: default
+ force: True
+ wait: True
+ replace: True
+ update_repo_cache: True
+ disable_hook: True
+ values:
+ global:
+ licenseKey: "{{ nr_license_key }}"
+ cluster: "{{ site_name }}"
+ newrelic-infrastructure:
+ privileged: True
+ ksm:
+ enabled: True
+ prometheus:
+ enabled: True
+ kubeEvents:
+ enabled: True
+ logging:
+ enabled: True
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm upgrade ...</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <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>on success Creation/Upgrade/Already deploy</td>
+ <td>
+ <div>A dictionary of status output</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>appversion</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Version of app deployed</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>chart</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Chart name and chart version</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Name of the release</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Namespace where the release is deployed</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>revision</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Number of time where the release has been updated</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">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Status of release (can be DEPLOYED, FAILED, ...)</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>updated</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>The Date of last update</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>values</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Dict of Values used to deploy</div>
+ <br/>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stderr, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stdout, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Lucas Boisserie (@LucasBoisserie)
+- Matthieu Diehr (@d-matt)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_info_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_info_module.rst
new file mode 100644
index 00000000..86c563f9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_info_module.rst
@@ -0,0 +1,300 @@
+.. _kubernetes.core.helm_plugin_info_module:
+
+
+********************************
+kubernetes.core.helm_plugin_info
+********************************
+
+**Gather information about Helm plugins**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Gather information about Helm plugins installed in namespace.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm (https://github.com/helm/helm/releases)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via <code>K8S_AUTH_API_KEY</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via <code>K8S_AUTH_SSL_CA_CERT</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify which kubeconfig context to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_CONTEXT</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via <code>K8S_AUTH_HOST</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify kubeconfig path to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>plugin_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of Helm plugin, to gather particular plugin info.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via <code>K8S_AUTH_VERIFY_SSL</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Gather Helm plugin info
+ kubernetes.core.helm_plugin_info:
+
+ - name: Gather Helm env plugin info
+ kubernetes.core.helm_plugin_info:
+ plugin_name: env
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm plugin list ...</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>plugin_list</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Helm plugin dict inside a list</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">{&#x27;name&#x27;: &#x27;env&#x27;, &#x27;version&#x27;: &#x27;0.1.0&#x27;, &#x27;description&#x27;: &#x27;Print out the helm environment.&#x27;}</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rc</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Helm plugin command return code</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">1</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stderr, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stdout, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Abhijeet Kasurde (@Akasurde)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_module.rst
new file mode 100644
index 00000000..ca1f1d03
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_plugin_module.rst
@@ -0,0 +1,346 @@
+.. _kubernetes.core.helm_plugin_module:
+
+
+***************************
+kubernetes.core.helm_plugin
+***************************
+
+**Manage Helm plugins**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages Helm plugins.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm (https://github.com/helm/helm/releases)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via <code>K8S_AUTH_API_KEY</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via <code>K8S_AUTH_SSL_CA_CERT</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify which kubeconfig context to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_CONTEXT</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kube_context</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via <code>K8S_AUTH_HOST</code> environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Helm option to specify kubeconfig path to use.</div>
+ <div>If the value is not specified in the task, the value of environment variable <code>K8S_AUTH_KUBECONFIG</code> will be used instead.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubeconfig_path</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>plugin_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of Helm plugin.</div>
+ <div>Required only if <code>state=absent</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>plugin_path</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>Plugin path to a plugin on your local file system or a url of a remote VCS repo.</div>
+ <div>If plugin path from file system is provided, make sure that tar is present on remote machine and not on Ansible controller.</div>
+ <div>Required only if <code>state=present</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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>absent</li>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>If <code>state=present</code> the Helm plugin will be installed.</div>
+ <div>If <code>state=absent</code> the Helm plugin will be removed.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via <code>K8S_AUTH_VERIFY_SSL</code> environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Install Helm env plugin
+ kubernetes.core.helm_plugin:
+ plugin_path: https://github.com/adamreese/helm-env
+ state: present
+
+ - name: Install Helm plugin from local filesystem
+ kubernetes.core.helm_plugin:
+ plugin_path: https://domain/path/to/plugin.tar.gz
+ state: present
+
+ - name: Remove Helm env plugin
+ kubernetes.core.helm_plugin:
+ plugin_name: env
+ state: absent
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm plugin list ...</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>msg</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Info about successful command</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">Plugin installed successfully</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rc</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Helm plugin command return code</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">1</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stderr, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stdout, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Abhijeet Kasurde (@Akasurde)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_pull_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_pull_module.rst
new file mode 100644
index 00000000..1c1af065
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_pull_module.rst
@@ -0,0 +1,467 @@
+.. _kubernetes.core.helm_pull_module:
+
+
+*************************
+kubernetes.core.helm_pull
+*************************
+
+**download a chart from a repository and (optionally) unpack it in local directory.**
+
+
+Version added: 2.4.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Retrieve a package from a package repository, and download it locally.
+- It can also be used to perform cryptographic verification of a chart without installing the chart.
+- There are options for unpacking the chart after download.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm >= 3.0 (https://github.com/helm/helm/releases)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Verify certificates of HTTPS-enabled servers using this CA bundle.</div>
+ <div>Requires helm &gt;= 3.1.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_devel</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Use development versions, too. Equivalent to version &#x27;&gt;0.0.0-0&#x27;.</div>
+ <div>Mutually exclusive with <code>chart_version</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ref</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>chart name on chart repository.</div>
+ <div>absolute URL.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ssl_cert_file</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Identify HTTPS client using this SSL certificate file.</div>
+ <div>Requires helm &gt;= 3.1.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ssl_key_file</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Identify HTTPS client using this SSL key file</div>
+ <div>Requires helm &gt;= 3.1.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specify a version constraint for the chart version to use.</div>
+ <div>This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).</div>
+ <div>Mutually exclusive with <code>chart_devel</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>location to write the chart.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>pass_credentials</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Pass credentials to all domains.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>provenance</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Fetch the provenance file, but don&#x27;t perform verification.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository password where to locate the requested chart.</div>
+ <div>Required if <code>repo_username</code> is specified.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: password, chart_repo_password</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_url</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>chart repository url where to locate the requested chart.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: url, chart_repo_url</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_username</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository username where to locate the requested chart.</div>
+ <div>Required if <code>repo_password</code> is specified.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: username, chart_repo_username</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>skip_tls_certs_check</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to check tls certificate for the chart download.</div>
+ <div>Requires helm &gt;= 3.3.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>untar_chart</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>if set to true, will untar the chart after downloading it.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>verify_chart</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Verify the package before using it.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>verify_chart_keyring</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>location of public keys used for verification.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Download chart using chart url
+ kubernetes.core.helm_pull:
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: /path/to/chart
+
+ - name: Download Chart using chart_name and repo_url
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+
+ - name: Download Chart (skip tls certificate check)
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+ skip_tls_certs_check: yes
+
+ - name: Download Chart using chart registry credentials
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+ username: myuser
+ password: mypassword123
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm pull --repo test ...</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rc</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Helm pull command return code</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">1</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm pull` command stderr, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm pull` command stdout, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Aubin Bikouo (@abikouo)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_repository_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_repository_module.rst
new file mode 100644
index 00000000..aa39ede6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_repository_module.rst
@@ -0,0 +1,291 @@
+.. _kubernetes.core.helm_repository_module:
+
+
+*******************************
+kubernetes.core.helm_repository
+*******************************
+
+**Manage Helm repositories.**
+
+
+Version added: 0.11.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manage Helm repositories.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- helm (https://github.com/helm/helm/releases)
+- yaml (https://pypi.org/project/PyYAML/)
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_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>Chart repository name.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: name</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository password for repository with basic auth.</div>
+ <div>Required if chart_repo_username is specified.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: password</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ <li>absent</li>
+ </ul>
+ </td>
+ <td>
+ <div>Desired state of repository.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: state</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_url</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository url</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: url</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>repo_username</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository username for repository with basic auth.</div>
+ <div>Required if chart_repo_password is specified.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: username</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Add a repository
+ kubernetes.core.helm_repository:
+ name: stable
+ repo_url: https://kubernetes.github.io/ingress-nginx
+
+ - name: Add Red Hat Helm charts repository
+ kubernetes.core.helm_repository:
+ name: redhat-charts
+ repo_url: https://redhat-developer.github.com/redhat-helm-charts
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">/usr/local/bin/helm repo add bitnami https://charts.bitnami.com/bitnami</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>msg</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>on failure</td>
+ <td>
+ <div>Error message returned by `helm` command</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">Repository already have a repository named bitnami</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stderr, in case you want to display it or examine the event log</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr_lines</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stderr in list, in case you want to display it or examine the event log</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&#x27;&#x27;]</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stdout, in case you want to display it or examine the event log</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">&quot;bitnami&quot; has been added to your repositories</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout_lines</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full `helm` command stdout in list, in case you want to display it or examine the event log</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&#x27;&quot;bitnami&quot; has been added to your repositories&#x27;]</div>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Lucas Boisserie (@LucasBoisserie)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_template_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_template_module.rst
new file mode 100644
index 00000000..67e53716
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.helm_template_module.rst
@@ -0,0 +1,310 @@
+.. _kubernetes.core.helm_template_module:
+
+
+*****************************
+kubernetes.core.helm_template
+*****************************
+
+**Render chart templates**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Render chart templates to an output directory or as text of concatenated yaml documents.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>binary_path</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The path of a helm binary to use.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_ref</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>Chart reference with repo prefix, for example, <code>nginx-stable/nginx-ingress</code>.</div>
+ <div>Path to a packaged chart.</div>
+ <div>Path to an unpacked chart directory.</div>
+ <div>Absolute URL.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_repo_url</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart repository URL where the requested chart is located.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>chart_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Chart version to use. If this is not specified, the latest version is installed.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>dependency_update</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Run helm dependency update before the operation.</div>
+ <div>The <em>dependency_update</em> option require the add of <code>dependencies</code> block in <code>Chart.yaml/requirements.yaml</code> file.</div>
+ <div>For more information please visit <a href='https://helm.sh/docs/helm/helm_dependency/'>https://helm.sh/docs/helm/helm_dependency/</a></div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: dep_up</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>include_crds</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>Include custom resource descriptions in rendered templates.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>output_dir</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>Output directory where templates will be written.</div>
+ <div>If the directory already exists, it will be overwritten.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>release_values</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">{}</div>
+ </td>
+ <td>
+ <div>Values to pass to chart.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: values</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>update_repo_cache</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <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>Run <code>helm repo update</code> before the operation. Can be run as part of the template generation or as a separate step.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>values_files</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>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">[]</div>
+ </td>
+ <td>
+ <div>Value files to pass to chart.</div>
+ <div>Paths will be read from the target host&#x27;s filesystem, not the host running ansible.</div>
+ <div><em>values_files</em> option is evaluated before <em>values</em> option if both are used.</div>
+ <div>Paths are evaluated in the order the paths are specified.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Render templates to specified directory
+ kubernetes.core.helm_template:
+ chart_ref: stable/prometheus
+ output_dir: mycharts
+
+ - name: Render templates
+ kubernetes.core.helm_template:
+ chart_ref: stable/prometheus
+ register: result
+
+ - name: Write templates to file
+ copy:
+ dest: myfile.yaml
+ content: "{{ result.stdout }}"
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>command</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full <code>helm</code> command run by this module, in case you want to re-run the command outside the module or debug a problem.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">helm template --output-dir mychart nginx-stable/nginx-ingress</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full <code>helm</code> command stderr, in case you want to display it or examine the event log.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Full <code>helm</code> command stdout. If no <em>output_dir</em> has been provided this will contain the rendered templates as concatenated yaml documents.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Mike Graves (@gravesm)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cluster_info_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cluster_info_module.rst
new file mode 100644
index 00000000..5e42983c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cluster_info_module.rst
@@ -0,0 +1,709 @@
+.. _kubernetes.core.k8s_cluster_info_module:
+
+
+********************************
+kubernetes.core.k8s_cluster_info
+********************************
+
+**Describe Kubernetes (K8s) cluster, APIs available and their respective versions**
+
+
+Version added: 0.11.1
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to perform read operations on K8s objects.
+- Authenticate using either a config file, certificates, password or token.
+- Supports check mode.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>invalidate_cache</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><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Invalidate cache before retrieving information about cluster.</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>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>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: Get Cluster information
+ kubernetes.core.k8s_cluster_info:
+ register: api_status
+
+ - name: Do not invalidate cache before getting information
+ kubernetes.core.k8s_cluster_info:
+ invalidate_cache: False
+ register: api_status
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>apis</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>dictionary of group + version of resource found from cluster</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>categories</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>API categories</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>name</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>Resource short name</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>namespaced</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>If resource is namespaced</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>preferred</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>If resource version preferred</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>short_names</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>Resource short names</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>singular_name</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>Resource singular name</div>
+ <br/>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>connection</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Connection information</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>cert_file</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>Path to client certificate.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>host</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>Host URL</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>password</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>User password</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>proxy</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>Proxy details</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>ssl_ca_cert</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>Path to CA certificate</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>username</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>Username</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>verify_ssl</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>SSL verification status</div>
+ <br/>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>version</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Information about server and client version</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>client</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>Client version</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>server</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Server version</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Abhijeet Kasurde (@Akasurde)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cp_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cp_module.rst
new file mode 100644
index 00000000..c61e1c12
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_cp_module.rst
@@ -0,0 +1,589 @@
+.. _kubernetes.core.k8s_cp_module:
+
+
+**********************
+kubernetes.core.k8s_cp
+**********************
+
+**Copy files and directories to and from pod.**
+
+
+Version added: 2.2.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to copy files and directories to and from containers inside a pod.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>container</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of the container in the pod to copy files/directories from/to.</div>
+ <div>Defaults to the only container if there is only one container in the pod.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>content</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>When used instead of <em>local_path</em>, sets the contents of a local file directly to the specified value.</div>
+ <div>Works only when <em>remote_path</em> is a file. Creates the file if it does not exist.</div>
+ <div>For advanced formatting or if the content contains a variable, use the <span class='module'>ansible.builtin.template</span> module.</div>
+ <div>Mutually exclusive with <em>local_path</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">raw</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ <div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version &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)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_drain_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_drain_module.rst
new file mode 100644
index 00000000..fa6e5ffb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_drain_module.rst
@@ -0,0 +1,616 @@
+.. _kubernetes.core.k8s_drain_module:
+
+
+*************************
+kubernetes.core.k8s_drain
+*************************
+
+**Drain, Cordon, or Uncordon node in k8s cluster**
+
+
+Version added: 2.2.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Drain node in preparation for maintenance same as kubectl drain.
+- Cordon will mark the node as unschedulable.
+- Uncordon will mark the node as schedulable.
+- The given node will be marked unschedulable to prevent new pods from arriving.
+- Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>delete_options</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specify options to delete pods.</div>
+ <div>This option has effect only when <code>state</code> is set to <em>drain</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>delete_emptydir_data</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained)</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable_eviction</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Forces drain to use delete rather than evict.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>force</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ignore_daemonsets</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Ignore DaemonSet-managed pods.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>terminate_grace_period</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specify how many seconds to wait before forcefully terminating.</div>
+ <div>If not specified, the default grace period for the object type will be used.</div>
+ <div>The value zero indicates delete immediately.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait_sleep</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">5</div>
+ </td>
+ <td>
+ <div>Number of seconds to sleep between checks.</div>
+ <div>Ignored if <code>wait_timeout</code> is not set.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait_timeout</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">raw</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ <div>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)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_exec_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_exec_module.rst
new file mode 100644
index 00000000..a928352e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_exec_module.rst
@@ -0,0 +1,590 @@
+.. _kubernetes.core.k8s_exec_module:
+
+
+************************
+kubernetes.core.k8s_exec
+************************
+
+**Execute command in Pod**
+
+
+Version added: 0.10.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to execute command on K8s pods.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>command</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 command to execute</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 connect to.</div>
+ <div>Defaults to 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>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">raw</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ <div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version &gt;= 17.17.0. Added in version 2.2.0.</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>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.</div>
+ <div>Can also be specified via <em>K8S_AUTH_PROXY</em> environment variable.</div>
+ <div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
+ </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>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::
+ - 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.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Execute a command
+ kubernetes.core.k8s_exec:
+ namespace: myproject
+ pod: zuul-scheduler
+ command: zuul-scheduler full-reconfigure
+
+ - name: Check RC status of command executed
+ kubernetes.core.k8s_exec:
+ namespace: myproject
+ pod: busybox-test
+ command: cmd_with_non_zero_exit_code
+ register: command_status
+ ignore_errors: True
+
+ - name: Check last command status
+ debug:
+ msg: "cmd failed"
+ when: command_status.rc != 0
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The command object</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rc</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
+ </td>
+ <td></td>
+ <td>
+ <div>The command status code</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>return_code</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td></td>
+ <td>
+ <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>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td></td>
+ <td>
+ <div>The command stderr</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stderr_lines</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td></td>
+ <td>
+ <div>The command stderr</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td></td>
+ <td>
+ <div>The command stdout</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>stdout_lines</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td></td>
+ <td>
+ <div>The command stdout</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Tristan de Cacqueray (@tristanC)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_info_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_info_module.rst
new file mode 100644
index 00000000..e4d248af
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_info_module.rst
@@ -0,0 +1,801 @@
+.. _kubernetes.core.k8s_info_module:
+
+
+************************
+kubernetes.core.k8s_info
+************************
+
+**Describe Kubernetes (K8s) objects**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to perform read operations on K8s objects.
+- Access to the full range of K8s APIs.
+- Authenticate using either a config file, certificates, password or token.
+- Supports check mode.
+- This module was called ``k8s_facts`` before Ansible 2.9. The usage did not change.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>apiVersion</em> value from the <em>resource_definition</em> will override this option.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</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>field_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>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of field selectors to use to filter results</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>Use to specify an object model.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>kind</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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>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>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of label selectors to use to filter results</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object name.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em> and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.name</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Useful when creating, deleting, or discovering an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </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>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>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to wait for certain resource kinds to end up in the desired state.</div>
+ <div>By default the module exits once Kubernetes has received the request.</div>
+ <div>Implemented for <code>state=present</code> for <code>Deployment</code>, <code>DaemonSet</code> and <code>Pod</code>, and for <code>state=absent</code> for all resource kinds.</div>
+ <div>For resource kinds without an implementation, <code>wait</code> returns immediately unless <code>wait_condition</code> is set.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait_condition</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>Specifies a custom condition on the status to wait for.</div>
+ <div>Ignored if <code>wait</code> is not set or is set to False.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>reason</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 value of the reason field in your desired condition</div>
+ <div>For example, if a <code>Deployment</code> is paused, The <code>Progressing</code> <code>type</code> will have the <code>DeploymentPaused</code> reason.</div>
+ <div>The possible reasons in a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>status</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>True</b>&nbsp;&larr;</div></li>
+ <li>False</li>
+ <li>Unknown</li>
+ </ul>
+ </td>
+ <td>
+ <div>The value of the status field in your desired condition.</div>
+ <div>For example, if a <code>Deployment</code> is paused, the <code>Progressing</code> <code>type</code> will have the <code>Unknown</code> status.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</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 type of condition to wait for.</div>
+ <div>For example, the <code>Pod</code> resource will set the <code>Ready</code> condition (among others).</div>
+ <div>Required if you are specifying a <code>wait_condition</code>.</div>
+ <div>If left empty, the <code>wait_condition</code> field will be ignored.</div>
+ <div>The possible types for a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <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>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <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>
+ <b>Default:</b><br/><div style="color: blue">120</div>
+ </td>
+ <td>
+ <div>How long in seconds to wait for the resource to end up in the desired state.</div>
+ <div>Ignored if <code>wait</code> is not set.</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: Get an existing Service object
+ kubernetes.core.k8s_info:
+ api_version: v1
+ kind: Service
+ name: web
+ namespace: testing
+ register: web_service
+
+ - name: Get a list of all service objects
+ kubernetes.core.k8s_info:
+ api_version: v1
+ kind: Service
+ namespace: testing
+ register: service_list
+
+ - name: Get a list of all pods from any namespace
+ kubernetes.core.k8s_info:
+ kind: Pod
+ register: pod_list
+
+ - name: Search for all Pods labelled app=web
+ kubernetes.core.k8s_info:
+ kind: Pod
+ label_selectors:
+ - app = web
+ - tier in (dev, test)
+
+ - name: Using vars while using label_selectors
+ kubernetes.core.k8s_info:
+ kind: Pod
+ label_selectors:
+ - "app = {{ app_label_web }}"
+ vars:
+ app_label_web: web
+
+ - name: Search for all running pods
+ kubernetes.core.k8s_info:
+ kind: Pod
+ field_selectors:
+ - status.phase=Running
+
+ - name: List custom objects created using CRD
+ kubernetes.core.k8s_info:
+ kind: MyCustomObject
+ api_version: "stable.example.com/v1"
+
+ - name: Wait till the Object is created
+ kubernetes.core.k8s_info:
+ kind: Pod
+ wait: yes
+ name: pod-not-yet-created
+ namespace: default
+ wait_sleep: 10
+ wait_timeout: 360
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>api_found</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Whether the specified api_version and kind were successfully mapped to an existing API on the targeted cluster.</div>
+ <div>Version added 1.2.0.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>resources</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The object(s) that exists</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">dictionary</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">dictionary</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">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Current status details for the object.</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Will Thames (@willthames)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_inventory.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_inventory.rst
new file mode 100644
index 00000000..79769c1b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_inventory.rst
@@ -0,0 +1,359 @@
+.. _kubernetes.core.k8s_inventory:
+
+
+*******************
+kubernetes.core.k8s
+*******************
+
+**Kubernetes (K8s) inventory source**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Fetch containers and services for one or more clusters.
+- Groups by cluster name, namespace, namespace_services, namespace_pods, and labels.
+- Uses the kubectl connection plugin to access the Kubernetes cluster.
+- Uses k8s.(yml|yaml) YAML configuration file to set parameter values.
+
+
+
+Requirements
+------------
+The below requirements are needed on the local Ansible controller node that executes this inventory.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>Configuration</th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>connections</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>Optional list of cluster connection settings. If no connections are provided, the default <em>~/.kube/config</em> and active context will be used, and objects will be returned for all namespaces the active user is authorized to access.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. 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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </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 class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Optional name to assign to the cluster. If not provided, a name is constructed from the server and port.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>namespaces</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>List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized to access.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ </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>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>plugin</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>kubernetes.core.k8s</li>
+ <li>k8s</li>
+ <li>community.kubernetes.k8s</li>
+ </ul>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>token that ensures this is a source file for the &#x27;k8s&#x27; plugin.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ # File must be named k8s.yaml or k8s.yml
+
+ # Authenticate with token, and return all pods and services for all namespaces
+ plugin: kubernetes.core.k8s
+ connections:
+ - host: https://192.168.64.4:8443
+ api_key: xxxxxxxxxxxxxxxx
+ validate_certs: false
+
+ # Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
+ plugin: kubernetes.core.k8s
+ connections:
+ - namespaces:
+ - testing
+
+ # Use a custom config file, and a specific context.
+ plugin: kubernetes.core.k8s
+ connections:
+ - kubeconfig: /path/to/config
+ context: 'awx/192-168-64-4:8443/developer'
+
+
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Chris Houseknecht <@chouseknecht>
+- Fabian von Feilitzsch <@fabianvf>
+
+
+.. 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.
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_json_patch_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_json_patch_module.rst
new file mode 100644
index 00000000..926c8410
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_json_patch_module.rst
@@ -0,0 +1,755 @@
+.. _kubernetes.core.k8s_json_patch_module:
+
+
+******************************
+kubernetes.core.k8s_json_patch
+******************************
+
+**Apply JSON patch operations to existing objects**
+
+
+Version added: 2.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- This module is used to apply RFC 6902 JSON patch operations only.
+- Use the :ref:`kubernetes.core.k8s <kubernetes.core.k8s_module>` module for strategic merge or JSON merge operations.
+- The jsonpatch library is required for check mode.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+- jsonpatch
+
+
+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>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>Use to specify an object model.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</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>Use to specify an object name.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>namespace</em> to identify a specific object.</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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>patch</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of JSON patch operations.</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>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>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to wait for certain resource kinds to end up in the desired state.</div>
+ <div>By default the module exits once Kubernetes has received the request.</div>
+ <div>Implemented for <code>state=present</code> for <code>Deployment</code>, <code>DaemonSet</code> and <code>Pod</code>, and for <code>state=absent</code> for all resource kinds.</div>
+ <div>For resource kinds without an implementation, <code>wait</code> returns immediately unless <code>wait_condition</code> is set.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait_condition</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>Specifies a custom condition on the status to wait for.</div>
+ <div>Ignored if <code>wait</code> is not set or is set to False.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>reason</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 value of the reason field in your desired condition</div>
+ <div>For example, if a <code>Deployment</code> is paused, The <code>Progressing</code> <code>type</code> will have the <code>DeploymentPaused</code> reason.</div>
+ <div>The possible reasons in a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>status</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>True</b>&nbsp;&larr;</div></li>
+ <li>False</li>
+ <li>Unknown</li>
+ </ul>
+ </td>
+ <td>
+ <div>The value of the status field in your desired condition.</div>
+ <div>For example, if a <code>Deployment</code> is paused, the <code>Progressing</code> <code>type</code> will have the <code>Unknown</code> status.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</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 type of condition to wait for.</div>
+ <div>For example, the <code>Pod</code> resource will set the <code>Ready</code> condition (among others).</div>
+ <div>Required if you are specifying a <code>wait_condition</code>.</div>
+ <div>If left empty, the <code>wait_condition</code> field will be ignored.</div>
+ <div>The possible types for a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <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>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <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>
+ <b>Default:</b><br/><div style="color: blue">120</div>
+ </td>
+ <td>
+ <div>How long in seconds to wait for the resource to end up in the desired state.</div>
+ <div>Ignored if <code>wait</code> is not set.</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: Apply multiple patch operations to an existing Pod
+ kubernetes.core.k8s_json_patch:
+ kind: Pod
+ namespace: testing
+ name: mypod
+ patch:
+ - op: add
+ path: /metadata/labels/app
+ value: myapp
+ - op: replace
+ patch: /spec/containers/0/image
+ value: nginx
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>duration</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>when <code>wait</code> is true</td>
+ <td>
+ <div>Elapsed time of task in seconds.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">48</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>error</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>error</td>
+ <td>
+ <div>The error when patching the object.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">{&#x27;msg&#x27;: &#x27;Failed to import the required Python library (jsonpatch) ...&#x27;, &#x27;exception&#x27;: &#x27;Traceback (most recent call last): ...&#x27;}</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The modified object.</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>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">dictionary</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">dictionary</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">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Current status details for the object.</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Mike Graves (@gravesm)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_log_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_log_module.rst
new file mode 100644
index 00000000..a639fb7d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_log_module.rst
@@ -0,0 +1,579 @@
+.. _kubernetes.core.k8s_log_module:
+
+
+***********************
+kubernetes.core.k8s_log
+***********************
+
+**Fetch logs from Kubernetes resources**
+
+
+Version added: 0.10.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to perform read operations on K8s log endpoints.
+- Authenticate using either a config file, certificates, password or token.
+- Supports check mode.
+- Analogous to `kubectl logs` or `oc logs`
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>apiVersion</em> value from the <em>resource_definition</em> will override this option.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</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>Use to specify the container within a pod to grab the log from.</div>
+ <div>If there is only one container, this will default to that container.</div>
+ <div>If there is more than one container, this option is required.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>
+ <b>Default:</b><br/><div style="color: blue">"Pod"</div>
+ </td>
+ <td>
+ <div>Use to specify an object model.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If using <em>label_selectors</em>, cannot be overridden.</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>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>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of label selectors to use to filter results</div>
+ <div>Only one of <em>name</em> or <em>label_selectors</em> may be provided.</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object name.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em> and <em>namespace</em> to identify a specific object.</div>
+ <div>Only one of <em>name</em> or <em>label_selectors</em> may be provided.</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Useful when creating, deleting, or discovering an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </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>since_seconds</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>A relative time in seconds before the current time from which to show logs.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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: Get a log from a Pod
+ kubernetes.core.k8s_log:
+ name: example-1
+ namespace: testing
+ register: log
+
+ # This will get the log from the first Pod found matching the selector
+ - name: Log a Pod matching a label selector
+ kubernetes.core.k8s_log:
+ namespace: testing
+ label_selectors:
+ - app=example
+ register: log
+
+ # This will get the log from a single Pod managed by this Deployment
+ - name: Get a log from a Deployment
+ kubernetes.core.k8s_log:
+ api_version: apps/v1
+ kind: Deployment
+ namespace: testing
+ name: example
+ since_seconds: "4000"
+ register: log
+
+ # This will get the log from a single Pod managed by this DeploymentConfig
+ - name: Get a log from a DeploymentConfig
+ kubernetes.core.k8s_log:
+ api_version: apps.openshift.io/v1
+ kind: DeploymentConfig
+ namespace: testing
+ name: example
+ register: log
+
+
+
+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>log</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 text log of the object</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>log_lines</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The log of the object, split on newlines</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Fabian von Feilitzsch (@fabianvf)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_lookup.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_lookup.rst
new file mode 100644
index 00000000..9e428c8b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_lookup.rst
@@ -0,0 +1,557 @@
+.. _kubernetes.core.k8s_lookup:
+
+
+*******************
+kubernetes.core.k8s
+*******************
+
+**Query the K8s API**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Uses the Kubernetes Python client to fetch a specific object by name, all matching objects within a namespace, or all matching objects for all namespaces, as well as information about the cluster.
+- Provides access the full range of K8s APIs.
+- Enables authentication via config file, certificates, password or token.
+
+
+
+Requirements
+------------
+The below requirements are needed on the local Ansible controller node that executes this lookup.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>api_key</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>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_version</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">"v1"</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify the API version. If <em>resource definition</em> is provided, the <em>apiVersion</em> from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. 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="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </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="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </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="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>cluster_info</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>Use to specify the type of cluster information you are attempting to retrieve. Will take priority over all the other options.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </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="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>field_selector</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>Specific fields on which to query. Ignored when <em>resource_name</em> is provided.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </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="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object model. If <em>resource definition</em> is provided, the <em>kind</em> from a <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>label_selector</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>Additional labels to include in the query. Ignored when <em>resource_name</em> is provided.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Limit the objects returned to a specific namespace. If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resource_definition</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>Provide a YAML configuration for an object. NOTE: <em>kind</em>, <em>api_version</em>, <em>resource_name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the provided <em>resource_definition</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resource_name</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>Fetch a specific object by name. If <em>resource definition</em> is provided, the <em>metadata.name</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>src</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>Provide a path to a file containing a valid YAML definition of an object dated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>resource_name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
+ <div>Reads from the local file system. To read from the Ansible controller&#x27;s file system, use the file lookup plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to <em>resource_definition</em>. See Examples below.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ </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::
+ - 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
+--------
+
+.. code-block:: yaml
+
+ - name: Fetch a list of namespaces
+ set_fact:
+ projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
+
+ - name: Fetch all deployments
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
+
+ - name: Fetch all deployments in a namespace
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
+
+ - name: Fetch a specific deployment by name
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
+
+ - name: Fetch with label selector
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
+
+ # Use parameters from a YAML config
+
+ - name: Load config from the Ansible controller filesystem
+ set_fact:
+ config: "{{ lookup('file', 'service.yml') | from_yaml }}"
+
+ - name: Using the config (loaded from a file in prior task), fetch the latest version of the object
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}"
+
+ - name: Use a config from the local filesystem
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', src='service.yml') }}"
+
+
+
+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 API.</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
+~~~~~~~
+
+- Chris Houseknecht <@chouseknecht>
+- Fabian von Feilitzsch <@fabianvf>
+
+
+.. 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.
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_module.rst
new file mode 100644
index 00000000..9db23e05
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_module.rst
@@ -0,0 +1,1268 @@
+.. _kubernetes.core.k8s_module:
+
+
+*******************
+kubernetes.core.k8s
+*******************
+
+**Manage Kubernetes (K8s) objects**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to perform CRUD operations on K8s objects.
+- Pass the object definition from a source file or inline. See examples for reading files and using Jinja templates or vault-encrypted files.
+- Access to the full range of K8s APIs.
+- Use the :ref:`kubernetes.core.k8s_info <kubernetes.core.k8s_info_module>` module to obtain a list of items about an object of type ``kind``
+- Authenticate using either a config file, certificates, password or token.
+- Supports check mode.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+- jsonpatch
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="3">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>apiVersion</em> value from the <em>resource_definition</em> will override this option.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>append_hash</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to append a hash to a resource name for immutability purposes</div>
+ <div>Applies only to ConfigMap and Secret resources</div>
+ <div>The parameter will be silently ignored for other resource kinds</div>
+ <div>The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including the generated hash and append_hash=no)</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>apply</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><code>apply</code> compares the desired resource definition with the previously supplied resource definition, ignoring properties that are automatically generated</div>
+ <div><code>apply</code> works better with Services than &#x27;force=yes&#x27;</div>
+ <div>mutually exclusive with <code>merge_type</code></div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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="3">
+ <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="3">
+ <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="3">
+ <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="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>continue_on_error</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to continue on creation/deletion errors when multiple resources are defined.</div>
+ <div>This has no effect on the validation step which is controlled by the <code>validate.fail_on_error</code> parameter.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>delete_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>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 1.2.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configure behavior when deleting an object.</div>
+ <div>Only used when <em>state=absent</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>gracePeriodSeconds</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>Only implemented for Pod resources.</div>
+ <div>If not specified, the default grace period for the object type will be used.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>preconditions</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 condition that must be met for delete to proceed.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resourceVersion</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specify the resource version of the target object.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>uid</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specify the UID of the target object.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>propagationPolicy</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>Foreground</li>
+ <li>Background</li>
+ <li>Orphan</li>
+ </ul>
+ </td>
+ <td>
+ <div>Use to control how dependent objects are deleted.</div>
+ <div>If not specified, the default policy for the object type will be used. This may vary across object types.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">
+ <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>If set to <code>yes</code>, and <em>state</em> is <code>present</code>, an existing object will be replaced.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>Use to specify an object model.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>kind</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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="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>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>merge_type</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>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>json</li>
+ <li>merge</li>
+ <li>strategic-merge</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to override the default patch merge approach with a specific type. By default, the strategic merge will typically be used.</div>
+ <div>For example, Custom Resource Definitions typically aren&#x27;t updatable by the usual strategic merge. You may want to use <code>merge</code> if you see &quot;strategic merge patch format is not supported&quot;</div>
+ <div>See <a href='https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment'>https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment</a></div>
+ <div>If more than one <code>merge_type</code> is given, the merge_types will be tried in order. This defaults to <code>[&#x27;strategic-merge&#x27;, &#x27;merge&#x27;]</code>, which is ideal for using the same parameters on resource kinds that combine Custom Resources and built-in resources.</div>
+ <div>mutually exclusive with <code>apply</code></div>
+ <div><em>merge_type=json</em> is deprecated and will be removed in version 3.0.0. Please use <span class='module'>kubernetes.core.k8s_json_patch</span> instead.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object name.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em> and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.name</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Useful when creating, deleting, or discovering an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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="3">
+ <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="3">
+ <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="3">
+ <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="2">
+ <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="2">
+ <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="2">
+ <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="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resource_definition</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>
+ <div>Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating.</div>
+ <div>NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the provided <em>resource_definition</em>.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>src</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>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
+ <div>Reads from the local file system. To read from the Ansible controller&#x27;s file system, including vaulted files, use the file lookup plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to <em>resource_definition</em>. See Examples below.</div>
+ <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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>absent</li>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ <li>patched</li>
+ </ul>
+ </td>
+ <td>
+ <div>Determines if an object should be created, patched, or deleted. When set to <code>present</code>, an object will be created, if it does not already exist. If set to <code>absent</code>, an existing object will be deleted. If set to <code>present</code>, an existing object will be patched, if its attributes differ from those specified using <em>resource_definition</em> or <em>src</em>.</div>
+ <div><code>patched</code> state is an existing resource that has a given patch applied. If the resource doesn&#x27;t exist, silently skip it (do not raise an error).</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>template</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>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>
+ <div>Valid additional parameters -</div>
+ <div><code>newline_sequence</code> (str): Specify the newline sequence to use for templating files. valid choices are &quot;\n&quot;, &quot;\r&quot;, &quot;\r\n&quot;. Default value &quot;\n&quot;.</div>
+ <div><code>block_start_string</code> (str): The string marking the beginning of a block. Default value &quot;{%&quot;.</div>
+ <div><code>block_end_string</code> (str): The string marking the end of a block. Default value &quot;%}&quot;.</div>
+ <div><code>variable_start_string</code> (str): The string marking the beginning of a print statement. Default value &quot;{{&quot;.</div>
+ <div><code>variable_end_string</code> (str): The string marking the end of a print statement. Default value &quot;}}&quot;.</div>
+ <div><code>trim_blocks</code> (bool): Determine when newlines should be removed from blocks. When set to <code>yes</code> the first newline after a block is removed (block, not variable tag!). Default value is true.</div>
+ <div><code>lstrip_blocks</code> (bool): Determine when leading spaces and tabs should be stripped. When set to <code>yes</code> leading spaces and tabs are stripped from the start of a line to a block. This functionality requires Jinja 2.7 or newer. Default value is false.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate</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>how (if at all) to validate the resource definition against the kubernetes schema. Requires the kubernetes-validate python module.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>fail_on_error</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 to fail on validation errors.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>strict</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><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>whether to fail when passing unexpected properties</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>version of Kubernetes to validate against. defaults to Kubernetes server version</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">
+ <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>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to wait for certain resource kinds to end up in the desired state.</div>
+ <div>By default the module exits once Kubernetes has received the request.</div>
+ <div>Implemented for <code>state=present</code> for <code>Deployment</code>, <code>DaemonSet</code> and <code>Pod</code>, and for <code>state=absent</code> for all resource kinds.</div>
+ <div>For resource kinds without an implementation, <code>wait</code> returns immediately unless <code>wait_condition</code> is set.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait_condition</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>Specifies a custom condition on the status to wait for.</div>
+ <div>Ignored if <code>wait</code> is not set or is set to False.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>reason</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 value of the reason field in your desired condition</div>
+ <div>For example, if a <code>Deployment</code> is paused, The <code>Progressing</code> <code>type</code> will have the <code>DeploymentPaused</code> reason.</div>
+ <div>The possible reasons in a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>status</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>True</b>&nbsp;&larr;</div></li>
+ <li>False</li>
+ <li>Unknown</li>
+ </ul>
+ </td>
+ <td>
+ <div>The value of the status field in your desired condition.</div>
+ <div>For example, if a <code>Deployment</code> is paused, the <code>Progressing</code> <code>type</code> will have the <code>Unknown</code> status.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</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 type of condition to wait for.</div>
+ <div>For example, the <code>Pod</code> resource will set the <code>Ready</code> condition (among others).</div>
+ <div>Required if you are specifying a <code>wait_condition</code>.</div>
+ <div>If left empty, the <code>wait_condition</code> field will be ignored.</div>
+ <div>The possible types for a condition are specific to each resource type in Kubernetes.</div>
+ <div>See the API documentation of the status field for a given resource to see possible choices.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">
+ <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>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <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>
+ <b>Default:</b><br/><div style="color: blue">120</div>
+ </td>
+ <td>
+ <div>How long in seconds to wait for the resource to end up in the desired state.</div>
+ <div>Ignored if <code>wait</code> is not set.</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: Create a k8s namespace
+ kubernetes.core.k8s:
+ name: testing
+ api_version: v1
+ kind: Namespace
+ state: present
+
+ - name: Create a Service object from an inline definition
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: web
+ namespace: testing
+ labels:
+ app: galaxy
+ service: web
+ spec:
+ selector:
+ app: galaxy
+ service: web
+ ports:
+ - protocol: TCP
+ targetPort: 8000
+ name: port-8000-tcp
+ port: 8000
+
+ - name: Remove an existing Service object
+ kubernetes.core.k8s:
+ state: absent
+ api_version: v1
+ kind: Service
+ namespace: testing
+ name: web
+
+ # Passing the object definition from a file
+
+ - name: Create a Deployment by reading the definition from a local file
+ kubernetes.core.k8s:
+ state: present
+ src: /testing/deployment.yml
+
+ - name: >-
+ Read definition file from the Ansible controller file system.
+ If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
+ kubernetes.core.k8s:
+ state: present
+ definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
+
+ - name: Read definition template file from the Ansible controller file system
+ kubernetes.core.k8s:
+ state: present
+ template: '/testing/deployment.j2'
+
+ - name: Read definition template file from the Ansible controller file system that uses custom start/end strings
+ kubernetes.core.k8s:
+ state: present
+ template:
+ path: '/testing/deployment.j2'
+ 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
+ definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
+ validate:
+ fail_on_error: yes
+
+ - name: warn on validation errors, check for unexpected properties
+ kubernetes.core.k8s:
+ state: present
+ definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
+ validate:
+ fail_on_error: no
+ strict: yes
+
+ # Download and apply manifest
+ - name: Download metrics-server manifest to the cluster.
+ ansible.builtin.get_url:
+ url: https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+ dest: ~/metrics-server.yaml
+ mode: '0664'
+
+ - name: Apply metrics-server manifest to the cluster.
+ kubernetes.core.k8s:
+ state: present
+ src: ~/metrics-server.yaml
+
+ # Wait for a Deployment to pause before continuing
+ - name: Pause a Deployment.
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: example
+ namespace: testing
+ spec:
+ paused: True
+ wait: yes
+ wait_condition:
+ type: Progressing
+ status: Unknown
+ reason: DeploymentPaused
+
+ # Patch existing namespace : add label
+ - name: add label to existing namespace
+ kubernetes.core.k8s:
+ state: patched
+ kind: Namespace
+ name: patch_namespace
+ definition:
+ metadata:
+ labels:
+ support: patch
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The created, patched, or otherwise present object. Will be empty in the case of a deletion.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>api_version</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The versioned schema of this representation of an object.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>duration</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>when <code>wait</code> is true</td>
+ <td>
+ <div>elapsed time of task in seconds</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">48</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>error</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>error</td>
+ <td>
+ <div>error while trying to create/delete the object.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>items</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when resource_definition or src contains list of objects</td>
+ <td>
+ <div>Returned only when multiple yaml documents are passed to src or resource_definition</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
+~~~~~~~
+
+- Chris Houseknecht (@chouseknecht)
+- Fabian von Feilitzsch (@fabianvf)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_rollback_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_rollback_module.rst
new file mode 100644
index 00000000..99ff84b9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_rollback_module.rst
@@ -0,0 +1,602 @@
+.. _kubernetes.core.k8s_rollback_module:
+
+
+****************************
+kubernetes.core.k8s_rollback
+****************************
+
+**Rollback Kubernetes (K8S) Deployments and DaemonSets**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the Kubernetes Python client to perform the Rollback.
+- Authenticate using either a config file, certificates, password or token.
+- Similar to the ``kubectl rollout undo`` command.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>apiVersion</em> value from the <em>resource_definition</em> will override this option.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</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>field_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>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of field selectors to use to filter results.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>Use to specify an object model.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>kind</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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>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>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of label selectors to use to filter results.</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object name.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em> and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.name</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Useful when creating, deleting, or discovering an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </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>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: Rollback a failed deployment
+ kubernetes.core.k8s_rollback:
+ api_version: apps/v1
+ kind: Deployment
+ name: web
+ namespace: testing
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rollback_info</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The object that was rolled back.</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>code</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 HTTP Code of the response</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>Status</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">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Standard object metadata.</div>
+ <div>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>status</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Current status details for the object.</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Julien Huon (@julienhuon)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_scale_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_scale_module.rst
new file mode 100644
index 00000000..c1f56a08
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_scale_module.rst
@@ -0,0 +1,803 @@
+.. _kubernetes.core.k8s_scale_module:
+
+
+*************************
+kubernetes.core.k8s_scale
+*************************
+
+**Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Similar to the kubectl scale command. Use to set the number of replicas for a Deployment, ReplicaSet, or Replication Controller, or the parallelism attribute of a Job. Supports check mode.
+- ``wait`` parameter is not supported for Jobs.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+- PyYAML >= 3.11
+
+
+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>api_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">"v1"</div>
+ </td>
+ <td>
+ <div>Use to specify the API version.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>kind</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>apiVersion</em> value from the <em>resource_definition</em> will override this option.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: api, version</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>continue_on_error</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to continue on errors when multiple resources are defined.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>current_replicas</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>For Deployment, ReplicaSet, Replication Controller, only scale, if the number of existing replicas matches. In the case of a Job, update parallelism only if the current parallelism value matches.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kind</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>Use to specify an object model.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>name</em>, and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>kind</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>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>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.0.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of label selectors to use to filter results.</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>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object name.</div>
+ <div>Use to create, delete, or discover an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em> and <em>namespace</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.name</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>namespace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Use to specify an object namespace.</div>
+ <div>Useful when creating, deleting, or discovering an object without providing a full resource definition.</div>
+ <div>Use in conjunction with <em>api_version</em>, <em>kind</em>, and <em>name</em> to identify a specific object.</div>
+ <div>If <em>resource definition</em> is provided, the <em>metadata.namespace</em> value from the <em>resource_definition</em> will override this option.</div>
+ </td>
+ </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>replicas</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The desired number of replicas.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resource_definition</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>
+ <div>Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating.</div>
+ <div>NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the provided <em>resource_definition</em>.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>resource_version</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Only attempt to scale, if the current object version matches.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>src</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>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
+ <div>Reads from the local file system. To read from the Ansible controller&#x27;s file system, including vaulted files, use the file lookup plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to <em>resource_definition</em>. See Examples below.</div>
+ <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</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>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>wait</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><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>For Deployment, ReplicaSet, Replication Controller, wait for the status value of <em>ready_replicas</em> to change to the number of <em>replicas</em>. In the case of a Job, this option is ignored.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <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>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">5</div>
+ </td>
+ <td>
+ <div>Number of seconds to sleep between checks.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <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>
+ <b>Default:</b><br/><div style="color: blue">20</div>
+ </td>
+ <td>
+ <div>When <code>wait</code> is <em>True</em>, the number of seconds to wait for the <em>ready_replicas</em> status to equal <em>replicas</em>. If the status is not reached within the allotted time, an error will result. In the case of a Job, this option is ignored.</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: Scale deployment up, and extend timeout
+ kubernetes.core.k8s_scale:
+ api_version: v1
+ kind: Deployment
+ name: elastic
+ namespace: myproject
+ replicas: 3
+ wait_timeout: 60
+
+ - name: Scale deployment down when current replicas match
+ kubernetes.core.k8s_scale:
+ api_version: v1
+ kind: Deployment
+ name: elastic
+ namespace: myproject
+ current_replicas: 3
+ replicas: 2
+
+ - name: Increase job parallelism
+ kubernetes.core.k8s_scale:
+ api_version: batch/v1
+ kind: job
+ name: pi-with-timeout
+ namespace: testing
+ replicas: 2
+
+ # Match object using local file or inline definition
+
+ - name: Scale deployment based on a file from the local filesystem
+ kubernetes.core.k8s_scale:
+ src: /myproject/elastic_deployment.yml
+ replicas: 3
+ wait: no
+
+ - name: Scale deployment based on a template output
+ kubernetes.core.k8s_scale:
+ resource_definition: "{{ lookup('template', '/myproject/elastic_deployment.yml') | from_yaml }}"
+ replicas: 3
+ wait: no
+
+ - name: Scale deployment based on a file from the Ansible controller filesystem
+ kubernetes.core.k8s_scale:
+ resource_definition: "{{ lookup('file', '/myproject/elastic_deployment.yml') | from_yaml }}"
+ replicas: 3
+ wait: no
+
+ - name: Scale deployment using label selectors (continue operation in case error occured on one resource)
+ kubernetes.core.k8s_scale:
+ replicas: 3
+ kind: Deployment
+ namespace: test
+ label_selectors:
+ - app=test
+ continue_on_error: true
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>If a change was made, will return the patched object, otherwise returns the existing object.</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>duration</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>when <code>wait</code> is true</td>
+ <td>
+ <div>elapsed time of task in seconds</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">48</div>
+ </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
+~~~~~~~
+
+- Chris Houseknecht (@chouseknecht)
+- Fabian von Feilitzsch (@fabianvf)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_service_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_service_module.rst
new file mode 100644
index 00000000..2941b917
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_service_module.rst
@@ -0,0 +1,713 @@
+.. _kubernetes.core.k8s_service_module:
+
+
+***************************
+kubernetes.core.k8s_service
+***************************
+
+**Manage Services on Kubernetes**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use Kubernetes Python SDK to manage Services on Kubernetes
+
+
+
+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>apply</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><code>apply</code> compares the desired resource definition with the previously supplied resource definition, ignoring properties that are automatically generated</div>
+ <div><code>apply</code> works better with Services than &#x27;force=yes&#x27;</div>
+ <div>mutually exclusive with <code>merge_type</code></div>
+ </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>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>If set to <code>yes</code>, and <em>state</em> is <code>present</code>, an existing object will be replaced.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">raw</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ <div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version &gt;= 17.17.0. Added in version 2.2.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>merge_type</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>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>json</li>
+ <li>merge</li>
+ <li>strategic-merge</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether to override the default patch merge approach with a specific type. By default, the strategic merge will typically be used.</div>
+ <div>For example, Custom Resource Definitions typically aren&#x27;t updatable by the usual strategic merge. You may want to use <code>merge</code> if you see &quot;strategic merge patch format is not supported&quot;</div>
+ <div>See <a href='https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment'>https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment</a></div>
+ <div>If more than one <code>merge_type</code> is given, the merge_types will be tried in order</div>
+ <div>This defaults to <code>[&#x27;strategic-merge&#x27;, &#x27;merge&#x27;]</code>, which is ideal for using the same parameters on resource kinds that combine Custom Resources and built-in resources.</div>
+ </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>Use to specify a Service object name.</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>Use to specify a Service object namespace.</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>ports</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>A list of ports to expose.</div>
+ <div><a href='https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services'>https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services</a></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>resource_definition</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>
+ <div>Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating.</div>
+ <div>NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the provided <em>resource_definition</em>.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: definition, inline</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>selector</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>Label selectors identify objects this Service should apply to.</div>
+ <div><a href='https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/'>https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/</a></div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>src</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>Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually exclusive with <em>resource_definition</em>. NOTE: <em>kind</em>, <em>api_version</em>, <em>name</em>, and <em>namespace</em> will be overwritten by corresponding values found in the configuration read in from the <em>src</em> file.</div>
+ <div>Reads from the local file system. To read from the Ansible controller&#x27;s file system, including vaulted files, use the file lookup plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to <em>resource_definition</em>. See Examples below.</div>
+ <div>Mutually exclusive with <em>template</em> in case of <span class='module'>k8s</span> module.</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>absent</li>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Determines if an object should be created, patched, or deleted. When set to <code>present</code>, an object will be created, if it does not already exist. If set to <code>absent</code>, an existing object will be deleted. If set to <code>present</code>, an existing object will be patched, if its attributes differ from those specified using <em>resource_definition</em> or <em>src</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</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>NodePort</li>
+ <li>ClusterIP</li>
+ <li>LoadBalancer</li>
+ <li>ExternalName</li>
+ </ul>
+ </td>
+ <td>
+ <div>Specifies the type of Service to create.</div>
+ <div>See <a href='https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types'>https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types</a></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: Expose https port with ClusterIP
+ kubernetes.core.k8s_service:
+ state: present
+ name: test-https
+ namespace: default
+ ports:
+ - port: 443
+ protocol: TCP
+ selector:
+ key: special
+
+ - name: Expose https port with ClusterIP using spec
+ kubernetes.core.k8s_service:
+ state: present
+ name: test-https
+ namespace: default
+ inline:
+ spec:
+ ports:
+ - port: 443
+ protocol: TCP
+ selector:
+ key: special
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>api_version</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The versioned schema of this representation of an object.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>kind</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Always &#x27;Service&#x27;.</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
+~~~~~~~
+
+- KubeVirt Team (@kubevirt)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_taint_module.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_taint_module.rst
new file mode 100644
index 00000000..4c342a20
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.k8s_taint_module.rst
@@ -0,0 +1,660 @@
+.. _kubernetes.core.k8s_taint_module:
+
+
+*************************
+kubernetes.core.k8s_taint
+*************************
+
+**Taint a node in a Kubernetes/OpenShift cluster**
+
+
+Version added: 2.3.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
+- Untaint will remove taints from nodes as needed.
+
+
+
+Requirements
+------------
+The below requirements are needed on the host that executes this module.
+
+- python >= 3.6
+- kubernetes >= 12.0.0
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>api_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ca_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_cert</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>client_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">path</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>context</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>host</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_groups</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Group(s) to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>impersonate_user</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Username to impersonate for the operation.</div>
+ <div>Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubeconfig</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">raw</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Path to an existing Kubernetes config file. If not provided, and no other connection options are provided, the Kubernetes client will attempt to load the default configuration file from <em>~/.kube/config</em>. Can also be specified via K8S_AUTH_KUBECONFIG environment variable.</div>
+ <div>The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version &gt;= 17.17.0. Added in version 2.2.0.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The name of the node.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>no_proxy</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.3.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The comma separated list of hosts/domains/IP/CIDR that shouldn&#x27;t go through proxy. Can also be specified via K8S_AUTH_NO_PROXY environment variable.</div>
+ <div>Please note that this module does not pick up typical proxy settings from the environment (e.g. NO_PROXY).</div>
+ <div>This feature requires kubernetes&gt;=19.15.0. When kubernetes library is less than 19.15.0, it fails even no_proxy set in correct.</div>
+ <div>example value is &quot;localhost,.local,.example.com,127.0.0.1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16&quot;</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment variable.</div>
+ <div>Please read the description of the <code>username</code> option for a discussion of when this option is applicable.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>persist_config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to save the kube config refresh tokens. Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.</div>
+ <div>When the k8s context is using a user credentials with refresh tokens (like oidc or gke/gcloud auth), the token is refreshed by the k8s python client library but not saved by default. So the old refresh token can expire and the next auth might fail. Setting this flag to true will tell the k8s python client to save the new refresh token to the kube config file.</div>
+ <div>Default to false.</div>
+ <div>Please note that the current version of the k8s python client library does not support setting this flag to True yet.</div>
+ <div>The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>proxy</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.</div>
+ <div>Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>proxy_headers</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ <div style="font-style: italic; font-size: small; color: darkgreen">added in 2.0.0</div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The Header used for the HTTP proxy.</div>
+ <div>Documentation can be found here <a href='https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers'>https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers</a>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>basic_auth</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Colon-separated username:password for basic authentication header.</div>
+ <div>Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>proxy_basic_auth</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Colon-separated username:password for proxy basic authentication header.</div>
+ <div>Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>user_agent</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>String representing the user-agent you want, such as foo/1.0.</div>
+ <div>Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>replace</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>If <code>true</code>, allow taints to be replaced.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>present</b>&nbsp;&larr;</div></li>
+ <li>absent</li>
+ </ul>
+ </td>
+ <td>
+ <div>Determines whether to add or remove taints.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>taints</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List containing the taints.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>effect</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>NoSchedule</li>
+ <li>NoExecute</li>
+ <li>PreferNoSchedule</li>
+ </ul>
+ </td>
+ <td>
+ <div>The effect of the taint on Pods that do not tolerate the taint.</div>
+ <div>Required when <em>state=present</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The taint key to be applied to a node.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>value</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The taint value corresponding to the taint key.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>username</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment variable.</div>
+ <div>Please note that this only works with clusters configured to use HTTP Basic Auth. If your cluster has a different form of authentication (e.g. OAuth2 in OpenShift), this option will not work as expected and you should look into the <span class='module'>community.okd.k8s_auth</span> module, as that might do what you need.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL environment variable.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: verify_ssl</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - To avoid SSL certificate validation errors when ``validate_certs`` is *True*, the full certificate chain for the API server must be provided via ``ca_cert`` or in the kubeconfig file.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Taint node "foo"
+ kubernetes.core.k8s_taint:
+ state: present
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+
+ - name: Taint node "foo"
+ kubernetes.core.k8s_taint:
+ state: present
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+ value: "value1"
+ - effect: NoSchedule
+ key: "key1"
+ value: "value1"
+
+ - name: Remove taint from "foo".
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+ value: "value1"
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>result</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The tainted Node object. Will be empty in the case of a deletion.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>api_version</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>The versioned schema of this representation of an object.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>kind</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Represents the REST resource this object represents.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>metadata</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Standard object metadata. Includes name, namespace, annotations, labels, etc.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>spec</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Specific attributes of the object. Will vary based on the <em>api_version</em> and <em>kind</em>.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder">&nbsp;</td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>status</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">complex</span>
+ </div>
+ </td>
+ <td>success</td>
+ <td>
+ <div>Current status details for the object.</div>
+ <br/>
+ </td>
+ </tr>
+
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Alina Buzachis (@alinabuzachis)
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.kubectl_connection.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.kubectl_connection.rst
new file mode 100644
index 00000000..595ee405
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.kubectl_connection.rst
@@ -0,0 +1,361 @@
+.. _kubernetes.core.kubectl_connection:
+
+
+***********************
+kubernetes.core.kubectl
+***********************
+
+**Execute tasks in pods running on Kubernetes.**
+
+
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Use the kubectl exec command to run tasks in, or put/fetch files to, pods running on the Kubernetes container platform.
+
+
+
+Requirements
+------------
+The below requirements are needed on the local Ansible controller node that executes this connection.
+
+- kubectl (go binary)
+
+
+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>ca_cert</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>
+ <div>env:K8S_AUTH_SSL_CA_CERT</div>
+ <div>var: ansible_kubectl_ssl_ca_cert</div>
+ <div>var: ansible_kubectl_ca_cert</div>
+ </td>
+ <td>
+ <div>Path to a CA certificate used to authenticate with the API.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubectl_ssl_ca_cert</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">""</div>
+ </td>
+ <td>
+ <div>env:K8S_AUTH_CERT_FILE</div>
+ <div>var: ansible_kubectl_cert_file</div>
+ <div>var: ansible_kubectl_client_cert</div>
+ </td>
+ <td>
+ <div>Path to a certificate used to authenticate with the API.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubectl_cert_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <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">-</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">""</div>
+ </td>
+ <td>
+ <div>env:K8S_AUTH_KEY_FILE</div>
+ <div>var: ansible_kubectl_key_file</div>
+ <div>var: ansible_kubectl_client_key</div>
+ </td>
+ <td>
+ <div>Path to a key file used to authenticate with the API.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubectl_key_file</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_container</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>
+ <div>env:K8S_AUTH_CONTAINER</div>
+ <div>var: ansible_kubectl_container</div>
+ </td>
+ <td>
+ <div>Container name.</div>
+ <div>Required when a pod contains more than one container.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_context</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>
+ <div>env:K8S_AUTH_CONTEXT</div>
+ <div>var: ansible_kubectl_context</div>
+ </td>
+ <td>
+ <div>The name of a context found in the K8s config file.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_extra_args</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>
+ <div>env:K8S_AUTH_EXTRA_ARGS</div>
+ <div>var: ansible_kubectl_extra_args</div>
+ </td>
+ <td>
+ <div>Extra arguments to pass to the kubectl command line.</div>
+ <div>Please be aware that this passes information directly on the command line and it could expose sensitive data.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_host</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>
+ <div>env:K8S_AUTH_HOST</div>
+ <div>env:K8S_AUTH_SERVER</div>
+ <div>var: ansible_kubectl_host</div>
+ <div>var: ansible_kubectl_server</div>
+ </td>
+ <td>
+ <div>URL for accessing the API.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_kubeconfig</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>
+ <div>env:K8S_AUTH_KUBECONFIG</div>
+ <div>var: ansible_kubectl_kubeconfig</div>
+ <div>var: ansible_kubectl_config</div>
+ </td>
+ <td>
+ <div>Path to a kubectl config file. Defaults to <em>~/.kube/config</em></div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_namespace</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>
+ <div>env:K8S_AUTH_NAMESPACE</div>
+ <div>var: ansible_kubectl_namespace</div>
+ </td>
+ <td>
+ <div>The namespace of the pod</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_password</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>
+ <div>env:K8S_AUTH_PASSWORD</div>
+ <div>var: ansible_kubectl_password</div>
+ </td>
+ <td>
+ <div>Provide a password for authenticating with the API.</div>
+ <div>Please be aware that this passes information directly on the command line and it could expose sensitive data. We recommend using the file based authentication options instead.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_pod</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>
+ <div>env:K8S_AUTH_POD</div>
+ <div>var: ansible_kubectl_pod</div>
+ </td>
+ <td>
+ <div>Pod name.</div>
+ <div>Required when the host name does not match pod name.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_token</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>
+ <div>env:K8S_AUTH_TOKEN</div>
+ <div>env:K8S_AUTH_API_KEY</div>
+ <div>var: ansible_kubectl_token</div>
+ <div>var: ansible_kubectl_api_key</div>
+ </td>
+ <td>
+ <div>API authentication bearer token.</div>
+ <div>Please be aware that this passes information directly on the command line and it could expose sensitive data. We recommend using the file based authentication options instead.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>kubectl_username</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>
+ <div>env:K8S_AUTH_USERNAME</div>
+ <div>var: ansible_kubectl_username</div>
+ <div>var: ansible_kubectl_user</div>
+ </td>
+ <td>
+ <div>Provide a username for authenticating with the API.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>validate_certs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">-</span>
+ </div>
+ </td>
+ <td>
+ <b>Default:</b><br/><div style="color: blue">""</div>
+ </td>
+ <td>
+ <div>env:K8S_AUTH_VERIFY_SSL</div>
+ <div>var: ansible_kubectl_verify_ssl</div>
+ <div>var: ansible_kubectl_validate_certs</div>
+ </td>
+ <td>
+ <div>Whether or not to verify the API server&#x27;s SSL certificate. Defaults to <em>true</em>.</div>
+ <div style="font-size: small; color: darkgreen"><br/>aliases: kubectl_verify_ssl</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+
+
+
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- xuxinkun
+
+
+.. 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.
diff --git a/ansible_collections/kubernetes/core/docs/kubernetes.core.kustomize_lookup.rst b/ansible_collections/kubernetes/core/docs/kubernetes.core.kustomize_lookup.rst
new file mode 100644
index 00000000..38d328ed
--- /dev/null
+++ b/ansible_collections/kubernetes/core/docs/kubernetes.core.kustomize_lookup.rst
@@ -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.
diff --git a/ansible_collections/kubernetes/core/meta/runtime.yml b/ansible_collections/kubernetes/core/meta/runtime.yml
new file mode 100644
index 00000000..6b6ebf57
--- /dev/null
+++ b/ansible_collections/kubernetes/core/meta/runtime.yml
@@ -0,0 +1,46 @@
+---
+requires_ansible: '>=2.9.17'
+
+action_groups:
+ helm:
+ - helm
+ - helm_info
+ - helm_repository
+ k8s:
+ - k8s
+ - k8s_exec
+ - k8s_info
+ - k8s_log
+ - k8s_scale
+ - k8s_service
+ - k8s_cp
+ - k8s_drain
+
+plugin_routing:
+ inventory:
+ openshift:
+ redirect: community.okd.openshift
+ modules:
+ k8s_auth:
+ redirect: community.okd.k8s_auth
+ k8s_facts:
+ tombstone:
+ removal_version: 2.0.0
+ warning_text: Use kubernetes.core.k8s_info instead.
+ k8s_raw:
+ tombstone:
+ removal_version: 0.1.0
+ warning_text: The k8s_raw module was slated for deprecation in Ansible 2.10 and has been removed. Use kubernetes.core.k8s instead.
+ openshift_raw:
+ tombstone:
+ removal_version: 0.1.0
+ warning_text: The openshift_raw module was slated for deprecation in Ansible 2.10 and has been removed. Use kubernetes.core.k8s instead.
+ openshift_scale:
+ tombstone:
+ removal_version: 0.1.0
+ warning_text: The openshift_scale module was slated for deprecation in Ansible 2.10 and has been removed. Use kubernetes.core.k8s_scale instead.
+ lookup:
+ openshift:
+ tombstone:
+ removal_version: 0.1.0
+ warning_text: The openshift lookup plugin was slated for deprecation in Ansible 2.10 and has been removed. Use kubernetes.core.k8s instead.
diff --git a/ansible_collections/kubernetes/core/plugins/action/helm.py b/ansible_collections/kubernetes/core/plugins/action/helm.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/helm.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/helm_info.py b/ansible_collections/kubernetes/core/plugins/action/helm_info.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/helm_info.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/helm_plugin.py b/ansible_collections/kubernetes/core/plugins/action/helm_plugin.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/helm_plugin.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/helm_plugin_info.py b/ansible_collections/kubernetes/core/plugins/action/helm_plugin_info.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/helm_plugin_info.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/helm_repository.py b/ansible_collections/kubernetes/core/plugins/action/helm_repository.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/helm_repository.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s.py b/ansible_collections/kubernetes/core/plugins/action/k8s.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_cluster_info.py b/ansible_collections/kubernetes/core/plugins/action/k8s_cluster_info.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_cluster_info.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_cp.py b/ansible_collections/kubernetes/core/plugins/action/k8s_cp.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_cp.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_drain.py b/ansible_collections/kubernetes/core/plugins/action/k8s_drain.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_drain.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_exec.py b/ansible_collections/kubernetes/core/plugins/action/k8s_exec.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_exec.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_info.py b/ansible_collections/kubernetes/core/plugins/action/k8s_info.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_info.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_log.py b/ansible_collections/kubernetes/core/plugins/action/k8s_log.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_log.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_rollback.py b/ansible_collections/kubernetes/core/plugins/action/k8s_rollback.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_rollback.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_scale.py b/ansible_collections/kubernetes/core/plugins/action/k8s_scale.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_scale.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/k8s_service.py b/ansible_collections/kubernetes/core/plugins/action/k8s_service.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/k8s_service.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/action/ks8_json_patch.py b/ansible_collections/kubernetes/core/plugins/action/ks8_json_patch.py
new file mode 100644
index 00000000..181daca4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/action/ks8_json_patch.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
+# Copyright (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import copy
+import traceback
+import os
+from contextlib import contextmanager
+import platform
+
+from ansible.config.manager import ensure_type
+from ansible.errors import (
+ AnsibleError,
+ AnsibleFileNotFound,
+ AnsibleAction,
+ AnsibleActionFail,
+)
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.six import string_types, iteritems
+from ansible.module_utils._text import to_text, to_bytes, to_native
+from ansible.plugins.action import ActionBase
+
+
+class RemoveOmit(object):
+ def __init__(self, buffer, omit_value):
+ try:
+ import yaml
+ except ImportError:
+ raise AnsibleError("Failed to import the required Python library (PyYAML).")
+ self.data = yaml.safe_load_all(buffer)
+ self.omit = omit_value
+
+ def remove_omit(self, data):
+ if isinstance(data, dict):
+ result = dict()
+ for key, value in iteritems(data):
+ if value == self.omit:
+ continue
+ result[key] = self.remove_omit(value)
+ return result
+ if isinstance(data, list):
+ return [self.remove_omit(v) for v in data if v != self.omit]
+ return data
+
+ def output(self):
+ return [self.remove_omit(d) for d in self.data]
+
+
+ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":"
+
+
+class ActionModule(ActionBase):
+
+ TRANSFERS_FILES = True
+ DEFAULT_NEWLINE_SEQUENCE = "\n"
+
+ def _ensure_invocation(self, result):
+ # NOTE: adding invocation arguments here needs to be kept in sync with
+ # any no_log specified in the argument_spec in the module.
+ if "invocation" not in result:
+ if self._play_context.no_log:
+ result["invocation"] = "CENSORED: no_log is set"
+ else:
+ result["invocation"] = self._task.args.copy()
+ result["invocation"]["module_args"] = self._task.args.copy()
+
+ return result
+
+ @contextmanager
+ def get_template_data(self, template_path):
+ try:
+ source = self._find_needle("templates", template_path)
+ except AnsibleError as e:
+ raise AnsibleActionFail(to_text(e))
+
+ # Get vault decrypted tmp file
+ try:
+ tmp_source = self._loader.get_real_file(source)
+ except AnsibleFileNotFound as e:
+ raise AnsibleActionFail(
+ "could not find template=%s, %s" % (source, to_text(e))
+ )
+ b_tmp_source = to_bytes(tmp_source, errors="surrogate_or_strict")
+
+ try:
+ with open(b_tmp_source, "rb") as f:
+ try:
+ template_data = to_text(f.read(), errors="surrogate_or_strict")
+ except UnicodeError:
+ raise AnsibleActionFail(
+ "Template source files must be utf-8 encoded"
+ )
+ yield template_data
+ except AnsibleAction:
+ raise
+ except Exception as e:
+ raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e)))
+ finally:
+ self._loader.cleanup_tmp_file(b_tmp_source)
+
+ def get_template_args(self, template):
+ template_param = {
+ "newline_sequence": self.DEFAULT_NEWLINE_SEQUENCE,
+ "variable_start_string": None,
+ "variable_end_string": None,
+ "block_start_string": None,
+ "block_end_string": None,
+ "trim_blocks": True,
+ "lstrip_blocks": False,
+ }
+ if isinstance(template, string_types):
+ # treat this as raw_params
+ template_param["path"] = template
+ elif isinstance(template, dict):
+ template_args = template
+ template_path = template_args.get("path", None)
+ if not template_path:
+ raise AnsibleActionFail("Please specify path for template.")
+ template_param["path"] = template_path
+
+ # Options type validation strings
+ for s_type in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ ):
+ if s_type in template_args:
+ value = ensure_type(template_args[s_type], "string")
+ if value is not None and not isinstance(value, string_types):
+ raise AnsibleActionFail(
+ "%s is expected to be a string, but got %s instead"
+ % (s_type, type(value))
+ )
+ try:
+ template_param.update(
+ {
+ "trim_blocks": boolean(
+ template_args.get("trim_blocks", True), strict=False
+ ),
+ "lstrip_blocks": boolean(
+ template_args.get("lstrip_blocks", False), strict=False
+ ),
+ }
+ )
+ except TypeError as e:
+ raise AnsibleActionFail(to_native(e))
+
+ template_param.update(
+ {
+ "newline_sequence": template_args.get(
+ "newline_sequence", self.DEFAULT_NEWLINE_SEQUENCE
+ ),
+ "variable_start_string": template_args.get(
+ "variable_start_string", None
+ ),
+ "variable_end_string": template_args.get(
+ "variable_end_string", None
+ ),
+ "block_start_string": template_args.get("block_start_string", None),
+ "block_end_string": template_args.get("block_end_string", None),
+ }
+ )
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+ return template_param
+
+ def import_jinja2_lstrip(self, templates):
+ # Option `lstrip_blocks' was added in Jinja2 version 2.7.
+ if any(tmp["lstrip_blocks"] for tmp in templates):
+ try:
+ import jinja2.defaults
+ except ImportError:
+ raise AnsibleError(
+ "Unable to import Jinja2 defaults for determining Jinja2 features."
+ )
+
+ try:
+ jinja2.defaults.LSTRIP_BLOCKS
+ except AttributeError:
+ raise AnsibleError(
+ "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7"
+ )
+
+ def load_template(self, template, new_module_args, task_vars):
+ # template is only supported by k8s module.
+ if self._task.action not in (
+ "k8s",
+ "kubernetes.core.k8s",
+ "community.okd.k8s",
+ "redhat.openshift.k8s",
+ "community.kubernetes.k8s",
+ "openshift_adm_groups_sync",
+ "community.okd.openshift_adm_groups_sync",
+ "redhat.openshift.openshift_adm_groups_sync",
+ ):
+ raise AnsibleActionFail(
+ "'template' is only a supported parameter for the 'k8s' module."
+ )
+
+ omit_value = task_vars.get("omit")
+ template_params = []
+ if isinstance(template, string_types) or isinstance(template, dict):
+ template_params.append(self.get_template_args(template))
+ elif isinstance(template, list):
+ for element in template:
+ template_params.append(self.get_template_args(element))
+ else:
+ raise AnsibleActionFail(
+ "Error while reading template file - "
+ "a string or dict for template expected, but got %s instead"
+ % type(template)
+ )
+
+ self.import_jinja2_lstrip(template_params)
+
+ wrong_sequences = ["\\n", "\\r", "\\r\\n"]
+ allowed_sequences = ["\n", "\r", "\r\n"]
+
+ result_template = []
+ old_vars = self._templar.available_variables
+
+ default_environment = {}
+ for key in (
+ "newline_sequence",
+ "variable_start_string",
+ "variable_end_string",
+ "block_start_string",
+ "block_end_string",
+ "trim_blocks",
+ "lstrip_blocks",
+ ):
+ if hasattr(self._templar.environment, key):
+ default_environment[key] = getattr(self._templar.environment, key)
+ for template_item in template_params:
+ # We need to convert unescaped sequences to proper escaped sequences for Jinja2
+ newline_sequence = template_item["newline_sequence"]
+ if newline_sequence in wrong_sequences:
+ template_item["newline_sequence"] = allowed_sequences[
+ wrong_sequences.index(newline_sequence)
+ ]
+ elif newline_sequence not in allowed_sequences:
+ raise AnsibleActionFail(
+ "newline_sequence needs to be one of: \n, \r or \r\n"
+ )
+
+ # template the source data locally & get ready to transfer
+ with self.get_template_data(template_item["path"]) as template_data:
+ # add ansible 'template' vars
+ temp_vars = copy.deepcopy(task_vars)
+ for key, value in iteritems(template_item):
+ if hasattr(self._templar.environment, key):
+ if value is not None:
+ setattr(self._templar.environment, key, value)
+ else:
+ setattr(
+ self._templar.environment,
+ key,
+ default_environment.get(key),
+ )
+ self._templar.available_variables = temp_vars
+ result = self._templar.do_template(
+ template_data,
+ preserve_trailing_newlines=True,
+ escape_backslashes=False,
+ )
+ if omit_value is not None:
+ result_template.extend(RemoveOmit(result, omit_value).output())
+ else:
+ result_template.append(result)
+ self._templar.available_variables = old_vars
+ resource_definition = self._task.args.get("definition", None)
+ if not resource_definition:
+ new_module_args.pop("template")
+ new_module_args["definition"] = result_template
+
+ def get_file_realpath(self, local_path):
+ # local_path is only supported by k8s_cp module.
+ if self._task.action not in (
+ "k8s_cp",
+ "kubernetes.core.k8s_cp",
+ "community.kubernetes.k8s_cp",
+ ):
+ raise AnsibleActionFail(
+ "'local_path' is only supported parameter for 'k8s_cp' module."
+ )
+
+ if os.path.exists(local_path):
+ return local_path
+
+ try:
+ # find in expected paths
+ return self._find_needle("files", local_path)
+ except AnsibleError:
+ raise AnsibleActionFail(
+ "%s does not exist in local filesystem" % local_path
+ )
+
+ def get_kubeconfig(self, kubeconfig, remote_transport, new_module_args):
+ if isinstance(kubeconfig, string_types):
+ # find the kubeconfig in the expected search path
+ if not remote_transport:
+ # kubeconfig is local
+ # find in expected paths
+ configs = []
+ for config in kubeconfig.split(ENV_KUBECONFIG_PATH_SEPARATOR):
+ config = self._find_needle("files", config)
+
+ # decrypt kubeconfig found
+ configs.append(self._loader.get_real_file(config, decrypt=True))
+ new_module_args["kubeconfig"] = ENV_KUBECONFIG_PATH_SEPARATOR.join(
+ configs
+ )
+
+ elif isinstance(kubeconfig, dict):
+ new_module_args["kubeconfig"] = kubeconfig
+ else:
+ raise AnsibleActionFail(
+ "Error while reading kubeconfig parameter - "
+ "a string or dict expected, but got %s instead" % type(kubeconfig)
+ )
+
+ def run(self, tmp=None, task_vars=None):
+ """handler for k8s options"""
+ if task_vars is None:
+ task_vars = dict()
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ # Check current transport connection and depending upon
+ # look for kubeconfig and src
+ # 'local' => look files on Ansible Controller
+ # Transport other than 'local' => look files on remote node
+ remote_transport = self._connection.transport != "local"
+
+ new_module_args = copy.deepcopy(self._task.args)
+
+ kubeconfig = self._task.args.get("kubeconfig", None)
+ if kubeconfig:
+ try:
+ 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
+
+ # find the file in the expected search path
+ src = self._task.args.get("src", None)
+
+ if src and not src.startswith(("http://", "https://", "ftp://")):
+ if remote_transport:
+ # src is on remote node
+ result.update(
+ self._execute_module(
+ module_name=self._task.action, task_vars=task_vars
+ )
+ )
+ return self._ensure_invocation(result)
+
+ # src is local
+ try:
+ # find in expected paths
+ src = self._find_needle("files", src)
+ except AnsibleError as e:
+ result["failed"] = True
+ result["msg"] = to_text(e)
+ result["exception"] = traceback.format_exc()
+ return result
+
+ if src:
+ new_module_args["src"] = src
+
+ template = self._task.args.get("template", None)
+ if template:
+ self.load_template(template, new_module_args, task_vars)
+
+ local_path = self._task.args.get("local_path")
+ state = self._task.args.get("state", None)
+ if local_path and state == "to_pod" and not remote_transport:
+ new_module_args["local_path"] = self.get_file_realpath(local_path)
+
+ # Execute the k8s_* module.
+ module_return = self._execute_module(
+ module_name=self._task.action,
+ module_args=new_module_args,
+ task_vars=task_vars,
+ )
+
+ # Delete tmp path
+ self._remove_tmp_path(self._connection._shell.tmpdir)
+
+ result.update(module_return)
+
+ return self._ensure_invocation(result)
diff --git a/ansible_collections/kubernetes/core/plugins/connection/kubectl.py b/ansible_collections/kubernetes/core/plugins/connection/kubectl.py
new file mode 100644
index 00000000..d0c3baa8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/connection/kubectl.py
@@ -0,0 +1,444 @@
+# Based on the docker connection plugin
+#
+# Connection plugin for configuring kubernetes containers with kubectl
+# (c) 2017, XuXinkun <xuxinkun@gmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+ author:
+ - xuxinkun (@xuxinkun)
+
+ name: kubectl
+
+ short_description: Execute tasks in pods running on Kubernetes.
+
+ description:
+ - Use the kubectl exec command to run tasks in, or put/fetch files to, pods running on the Kubernetes
+ container platform.
+
+ requirements:
+ - kubectl (go binary)
+
+ options:
+ kubectl_pod:
+ description:
+ - Pod name.
+ - Required when the host name does not match pod name.
+ default: ''
+ vars:
+ - name: ansible_kubectl_pod
+ env:
+ - name: K8S_AUTH_POD
+ kubectl_container:
+ description:
+ - Container name.
+ - Required when a pod contains more than one container.
+ default: ''
+ vars:
+ - name: ansible_kubectl_container
+ env:
+ - name: K8S_AUTH_CONTAINER
+ kubectl_namespace:
+ description:
+ - The namespace of the pod
+ default: ''
+ vars:
+ - name: ansible_kubectl_namespace
+ env:
+ - name: K8S_AUTH_NAMESPACE
+ kubectl_extra_args:
+ description:
+ - Extra arguments to pass to the kubectl command line.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ default: ''
+ vars:
+ - name: ansible_kubectl_extra_args
+ env:
+ - name: K8S_AUTH_EXTRA_ARGS
+ kubectl_kubeconfig:
+ description:
+ - Path to a kubectl config file. Defaults to I(~/.kube/config)
+ - The configuration can be provided as dictionary. Added in version 2.4.0.
+ default: ''
+ vars:
+ - name: ansible_kubectl_kubeconfig
+ - name: ansible_kubectl_config
+ env:
+ - name: K8S_AUTH_KUBECONFIG
+ kubectl_context:
+ description:
+ - The name of a context found in the K8s config file.
+ default: ''
+ vars:
+ - name: ansible_kubectl_context
+ env:
+ - name: K8S_AUTH_CONTEXT
+ kubectl_host:
+ description:
+ - URL for accessing the API.
+ default: ''
+ vars:
+ - name: ansible_kubectl_host
+ - name: ansible_kubectl_server
+ env:
+ - name: K8S_AUTH_HOST
+ - name: K8S_AUTH_SERVER
+ kubectl_username:
+ description:
+ - Provide a username for authenticating with the API.
+ default: ''
+ vars:
+ - name: ansible_kubectl_username
+ - name: ansible_kubectl_user
+ env:
+ - name: K8S_AUTH_USERNAME
+ kubectl_password:
+ description:
+ - Provide a password for authenticating with the API.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ We recommend using the file based authentication options instead.
+ default: ''
+ vars:
+ - name: ansible_kubectl_password
+ env:
+ - name: K8S_AUTH_PASSWORD
+ kubectl_token:
+ description:
+ - API authentication bearer token.
+ - Please be aware that this passes information directly on the command line and it could expose sensitive data.
+ We recommend using the file based authentication options instead.
+ vars:
+ - name: ansible_kubectl_token
+ - name: ansible_kubectl_api_key
+ env:
+ - name: K8S_AUTH_TOKEN
+ - name: K8S_AUTH_API_KEY
+ client_cert:
+ description:
+ - Path to a certificate used to authenticate with the API.
+ default: ''
+ vars:
+ - name: ansible_kubectl_cert_file
+ - name: ansible_kubectl_client_cert
+ env:
+ - name: K8S_AUTH_CERT_FILE
+ aliases: [ kubectl_cert_file ]
+ client_key:
+ description:
+ - Path to a key file used to authenticate with the API.
+ default: ''
+ vars:
+ - name: ansible_kubectl_key_file
+ - name: ansible_kubectl_client_key
+ env:
+ - name: K8S_AUTH_KEY_FILE
+ aliases: [ kubectl_key_file ]
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API.
+ default: ''
+ vars:
+ - name: ansible_kubectl_ssl_ca_cert
+ - name: ansible_kubectl_ca_cert
+ env:
+ - name: K8S_AUTH_SSL_CA_CERT
+ aliases: [ kubectl_ssl_ca_cert ]
+ validate_certs:
+ description:
+ - Whether or not to verify the API server's SSL certificate. Defaults to I(true).
+ default: ''
+ vars:
+ - name: ansible_kubectl_verify_ssl
+ - name: ansible_kubectl_validate_certs
+ env:
+ - name: K8S_AUTH_VERIFY_SSL
+ aliases: [ kubectl_verify_ssl ]
+"""
+
+import os
+import os.path
+import shutil
+import subprocess
+import tempfile
+import json
+
+from ansible.parsing.yaml.loader import AnsibleLoader
+from ansible.errors import AnsibleError, AnsibleFileNotFound
+from ansible.module_utils.six.moves import shlex_quote
+from ansible.module_utils._text import to_bytes
+from ansible.plugins.connection import ConnectionBase, BUFSIZE
+from ansible.utils.display import Display
+
+display = Display()
+
+
+CONNECTION_TRANSPORT = "kubectl"
+
+CONNECTION_OPTIONS = {
+ "kubectl_container": "-c",
+ "kubectl_namespace": "-n",
+ "kubectl_kubeconfig": "--kubeconfig",
+ "kubectl_context": "--context",
+ "kubectl_host": "--server",
+ "kubectl_username": "--username",
+ "kubectl_password": "--password",
+ "client_cert": "--client-certificate",
+ "client_key": "--client-key",
+ "ca_cert": "--certificate-authority",
+ "validate_certs": "--insecure-skip-tls-verify",
+ "kubectl_token": "--token",
+}
+
+
+class Connection(ConnectionBase):
+ """Local kubectl based connections"""
+
+ transport = CONNECTION_TRANSPORT
+ connection_options = CONNECTION_OPTIONS
+ documentation = DOCUMENTATION
+ has_pipelining = True
+ transport_cmd = None
+
+ def __init__(self, play_context, new_stdin, *args, **kwargs):
+ super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
+
+ # Note: kubectl runs commands as the user that started the container.
+ # It is impossible to set the remote user for a kubectl connection.
+ cmd_arg = "{0}_command".format(self.transport)
+ self.transport_cmd = kwargs.get(cmd_arg, shutil.which(self.transport))
+ if not self.transport_cmd:
+ raise AnsibleError("{0} command not found in PATH".format(self.transport))
+ self._file_to_delete = None
+
+ def delete_temporary_file(self):
+ if self._file_to_delete is not None:
+ os.remove(self._file_to_delete)
+ self._file_to_delete = None
+
+ def _build_exec_cmd(self, cmd):
+ """Build the local kubectl exec command to run cmd on remote_host"""
+ local_cmd = [self.transport_cmd]
+ censored_local_cmd = [self.transport_cmd]
+
+ # Build command options based on doc string
+ doc_yaml = AnsibleLoader(self.documentation).get_single_data()
+ for key in doc_yaml.get("options"):
+ if key.endswith("verify_ssl") and self.get_option(key) != "":
+ # Translate verify_ssl to skip_verify_ssl, and output as string
+ skip_verify_ssl = not self.get_option(key)
+ local_cmd.append(
+ "{0}={1}".format(
+ self.connection_options[key], str(skip_verify_ssl).lower()
+ )
+ )
+ censored_local_cmd.append(
+ "{0}={1}".format(
+ self.connection_options[key], str(skip_verify_ssl).lower()
+ )
+ )
+ elif key.endswith("kubeconfig") and self.get_option(key) != "":
+ kubeconfig_path = self.get_option(key)
+ if isinstance(kubeconfig_path, dict):
+ fd, tmpfile = tempfile.mkstemp()
+ with os.fdopen(fd, "w") as fp:
+ json.dump(kubeconfig_path, fp)
+ kubeconfig_path = tmpfile
+ self._file_to_delete = tmpfile
+
+ cmd_arg = self.connection_options[key]
+ local_cmd += [cmd_arg, kubeconfig_path]
+ censored_local_cmd += [cmd_arg, kubeconfig_path]
+ elif (
+ not key.endswith("container")
+ and self.get_option(key)
+ and self.connection_options.get(key)
+ ):
+ cmd_arg = self.connection_options[key]
+ local_cmd += [cmd_arg, self.get_option(key)]
+ # Redact password and token from console log
+ if key.endswith(("_token", "_password")):
+ censored_local_cmd += [cmd_arg, "********"]
+ else:
+ censored_local_cmd += [cmd_arg, self.get_option(key)]
+
+ extra_args_name = "{0}_extra_args".format(self.transport)
+ if self.get_option(extra_args_name):
+ local_cmd += self.get_option(extra_args_name).split(" ")
+ censored_local_cmd += self.get_option(extra_args_name).split(" ")
+
+ pod = self.get_option("{0}_pod".format(self.transport))
+ if not pod:
+ pod = self._play_context.remote_addr
+ # -i is needed to keep stdin open which allows pipelining to work
+ local_cmd += ["exec", "-i", pod]
+ censored_local_cmd += ["exec", "-i", pod]
+
+ # if the pod has more than one container, then container is required
+ container_arg_name = "{0}_container".format(self.transport)
+ if self.get_option(container_arg_name):
+ local_cmd += ["-c", self.get_option(container_arg_name)]
+ censored_local_cmd += ["-c", self.get_option(container_arg_name)]
+
+ local_cmd += ["--"] + cmd
+ censored_local_cmd += ["--"] + cmd
+
+ return local_cmd, censored_local_cmd
+
+ def _connect(self, port=None):
+ """Connect to the container. Nothing to do"""
+ super(Connection, self)._connect()
+ if not self._connected:
+ display.vvv(
+ "ESTABLISH {0} CONNECTION".format(self.transport),
+ host=self._play_context.remote_addr,
+ )
+ self._connected = True
+
+ def exec_command(self, cmd, in_data=None, sudoable=False):
+ """Run a command in the container"""
+ super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
+
+ local_cmd, censored_local_cmd = self._build_exec_cmd(
+ [self._play_context.executable, "-c", cmd]
+ )
+
+ display.vvv(
+ "EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr
+ )
+ local_cmd = [to_bytes(i, errors="surrogate_or_strict") for i in local_cmd]
+ p = subprocess.Popen(
+ local_cmd,
+ shell=False,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ stdout, stderr = p.communicate(in_data)
+ self.delete_temporary_file()
+ return (p.returncode, stdout, stderr)
+
+ def _prefix_login_path(self, remote_path):
+ """Make sure that we put files into a standard path
+
+ If a path is relative, then we need to choose where to put it.
+ ssh chooses $HOME but we aren't guaranteed that a home dir will
+ exist in any given chroot. So for now we're choosing "/" instead.
+ This also happens to be the former default.
+
+ Can revisit using $HOME instead if it's a problem
+ """
+ if not remote_path.startswith(os.path.sep):
+ remote_path = os.path.join(os.path.sep, remote_path)
+ return os.path.normpath(remote_path)
+
+ def put_file(self, in_path, out_path):
+ """Transfer a file from local to the container"""
+ super(Connection, self).put_file(in_path, out_path)
+ display.vvv(
+ "PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
+ )
+
+ out_path = self._prefix_login_path(out_path)
+ if not os.path.exists(to_bytes(in_path, errors="surrogate_or_strict")):
+ raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
+
+ out_path = shlex_quote(out_path)
+ # kubectl doesn't have native support for copying files into
+ # running containers, so we use kubectl exec to implement this
+ with open(to_bytes(in_path, errors="surrogate_or_strict"), "rb") as in_file:
+ if not os.fstat(in_file.fileno()).st_size:
+ count = " count=0"
+ else:
+ count = ""
+ args, dummy = self._build_exec_cmd(
+ [
+ self._play_context.executable,
+ "-c",
+ "dd of=%s bs=%s%s && sleep 0" % (out_path, BUFSIZE, count),
+ ]
+ )
+ args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
+ try:
+ p = subprocess.Popen(
+ args, stdin=in_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ except OSError:
+ raise AnsibleError(
+ "kubectl connection requires dd command in the container to put files"
+ )
+ stdout, stderr = p.communicate()
+ self.delete_temporary_file()
+
+ if p.returncode != 0:
+ raise AnsibleError(
+ "failed to transfer file %s to %s:\n%s\n%s"
+ % (in_path, out_path, stdout, stderr)
+ )
+
+ def fetch_file(self, in_path, out_path):
+ """Fetch a file from container to local."""
+ super(Connection, self).fetch_file(in_path, out_path)
+ display.vvv(
+ "FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr
+ )
+
+ in_path = self._prefix_login_path(in_path)
+ out_dir = os.path.dirname(out_path)
+
+ # kubectl doesn't have native support for fetching files from
+ # running containers, so we use kubectl exec to implement this
+ args, dummy = self._build_exec_cmd(
+ [self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)]
+ )
+ args = [to_bytes(i, errors="surrogate_or_strict") for i in args]
+ actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
+ with open(
+ to_bytes(actual_out_path, errors="surrogate_or_strict"), "wb"
+ ) as out_file:
+ try:
+ p = subprocess.Popen(
+ args, stdin=subprocess.PIPE, stdout=out_file, stderr=subprocess.PIPE
+ )
+ except OSError:
+ raise AnsibleError(
+ "{0} connection requires dd command in the container to fetch files".format(
+ self.transport
+ )
+ )
+ stdout, stderr = p.communicate()
+ self.delete_temporary_file()
+
+ if p.returncode != 0:
+ raise AnsibleError(
+ "failed to fetch file %s to %s:\n%s\n%s"
+ % (in_path, out_path, stdout, stderr)
+ )
+
+ if actual_out_path != out_path:
+ os.rename(
+ to_bytes(actual_out_path, errors="strict"),
+ to_bytes(out_path, errors="strict"),
+ )
+
+ def close(self):
+ """Terminate the connection. Nothing to do for kubectl"""
+ super(Connection, self).close()
+ self._connected = False
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/__init__.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/helm_common_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/helm_common_options.py
new file mode 100644
index 00000000..dde91db1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/helm_common_options.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible Project
+# Copyright: (c) 2020, Red Hat Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for common Helm modules
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ binary_path:
+ description:
+ - The path of a helm binary to use.
+ required: false
+ type: path
+ context:
+ description:
+ - Helm option to specify which kubeconfig context to use.
+ - If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
+ type: str
+ aliases: [ kube_context ]
+ kubeconfig:
+ description:
+ - Helm option to specify kubeconfig path to use.
+ - If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
+ - The configuration can be provided as dictionary. Added in version 2.4.0.
+ type: raw
+ aliases: [ kubeconfig_path ]
+ host:
+ description:
+ - Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
+ type: str
+ version_added: "1.2.0"
+ api_key:
+ description:
+ - Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable.
+ type: str
+ version_added: "1.2.0"
+ validate_certs:
+ description:
+ - Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL)
+ environment variable.
+ type: bool
+ aliases: [ verify_ssl ]
+ default: True
+ version_added: "1.2.0"
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
+ avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable.
+ type: path
+ aliases: [ ssl_ca_cert ]
+ version_added: "1.2.0"
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_auth_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_auth_options.py
new file mode 100644
index 00000000..516ef64f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_auth_options.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for authenticating with the API.
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ host:
+ description:
+ - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
+ type: str
+ api_key:
+ description:
+ - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.
+ type: str
+ kubeconfig:
+ description:
+ - 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 I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment
+ variable.
+ - Multiple Kubernetes config file can be provided using separator ';' for Windows platform or ':' for others platforms.
+ - The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.
+ type: raw
+ context:
+ description:
+ - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable.
+ type: str
+ username:
+ description:
+ - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment
+ variable.
+ - 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 M(community.okd.k8s_auth) module, as that might do what you need.
+ type: str
+ password:
+ description:
+ - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment
+ variable.
+ - Please read the description of the C(username) option for a discussion of when this option is applicable.
+ type: str
+ client_cert:
+ description:
+ - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment
+ variable.
+ type: path
+ aliases: [ cert_file ]
+ client_key:
+ description:
+ - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment
+ variable.
+ type: path
+ aliases: [ key_file ]
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
+ avoid certificate validation errors. Can also be specified via K8S_AUTH_SSL_CA_CERT environment variable.
+ type: path
+ aliases: [ ssl_ca_cert ]
+ validate_certs:
+ description:
+ - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL
+ environment variable.
+ type: bool
+ aliases: [ verify_ssl ]
+ proxy:
+ description:
+ - The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.
+ - Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
+ type: str
+ no_proxy:
+ description:
+ - The comma separated list of hosts/domains/IP/CIDR that shouldn't go through proxy. Can also be specified via K8S_AUTH_NO_PROXY environment variable.
+ - Please note that this module does not pick up typical proxy settings from the environment (e.g. NO_PROXY).
+ - This feature requires kubernetes>=19.15.0. When kubernetes library is less than 19.15.0, it fails even no_proxy set in correct.
+ - example value is "localhost,.local,.example.com,127.0.0.1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
+ type: str
+ version_added: 2.3.0
+ proxy_headers:
+ description:
+ - The Header used for the HTTP proxy.
+ - Documentation can be found here U(https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html?highlight=proxy_headers#urllib3.util.make_headers).
+ type: dict
+ version_added: 2.0.0
+ suboptions:
+ proxy_basic_auth:
+ type: str
+ description:
+ - Colon-separated username:password for proxy basic authentication header.
+ - Can also be specified via K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH environment.
+ basic_auth:
+ type: str
+ description:
+ - Colon-separated username:password for basic authentication header.
+ - Can also be specified via K8S_AUTH_PROXY_HEADERS_BASIC_AUTH environment.
+ user_agent:
+ type: str
+ description:
+ - String representing the user-agent you want, such as foo/1.0.
+ - Can also be specified via K8S_AUTH_PROXY_HEADERS_USER_AGENT environment.
+ persist_config:
+ description:
+ - Whether or not to save the kube config refresh tokens.
+ Can also be specified via K8S_AUTH_PERSIST_CONFIG environment variable.
+ - 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.
+ - Default to false.
+ - Please note that the current version of the k8s python client library does not support setting this flag to True yet.
+ - "The fix for this k8s python library is here: https://github.com/kubernetes-client/python-base/pull/169"
+ type: bool
+ impersonate_user:
+ description:
+ - Username to impersonate for the operation.
+ - Can also be specified via K8S_AUTH_IMPERSONATE_USER environment.
+ type: str
+ version_added: 2.3.0
+ impersonate_groups:
+ description:
+ - Group(s) to impersonate for the operation.
+ - "Can also be specified via K8S_AUTH_IMPERSONATE_GROUPS environment. Example: Group1,Group2"
+ type: list
+ elements: str
+ version_added: 2.3.0
+notes:
+ - "To avoid SSL certificate validation errors when C(validate_certs) is I(True), the full
+ certificate chain for the API server must be provided via C(ca_cert) or in the
+ kubeconfig file."
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_delete_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_delete_options.py
new file mode 100644
index 00000000..a8f20cf9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_delete_options.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for specifying object wait
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ delete_options:
+ type: dict
+ version_added: '1.2.0'
+ description:
+ - Configure behavior when deleting an object.
+ - Only used when I(state=absent).
+ suboptions:
+ propagationPolicy:
+ type: str
+ description:
+ - Use to control how dependent objects are deleted.
+ - If not specified, the default policy for the object type will be used. This may vary across object types.
+ choices:
+ - "Foreground"
+ - "Background"
+ - "Orphan"
+ gracePeriodSeconds:
+ type: int
+ description:
+ - Specify how many seconds to wait before forcefully terminating.
+ - Only implemented for Pod resources.
+ - If not specified, the default grace period for the object type will be used.
+ preconditions:
+ type: dict
+ description:
+ - Specify condition that must be met for delete to proceed.
+ suboptions:
+ resourceVersion:
+ type: str
+ description:
+ - Specify the resource version of the target object.
+ uid:
+ type: str
+ description:
+ - Specify the UID of the target object.
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_name_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_name_options.py
new file mode 100644
index 00000000..e14658b0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_name_options.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for selecting or identifying a specific K8s object
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ api_version:
+ description:
+ - Use to specify the API version.
+ - Use to create, delete, or discover an object without providing a full resource definition.
+ - Use in conjunction with I(kind), I(name), and I(namespace) to identify a specific object.
+ - If I(resource definition) is provided, the I(apiVersion) value from the I(resource_definition)
+ will override this option.
+ type: str
+ default: v1
+ aliases:
+ - api
+ - version
+ kind:
+ description:
+ - Use to specify an object model.
+ - Use to create, delete, or discover an object without providing a full resource definition.
+ - Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
+ - If I(resource definition) is provided, the I(kind) value from the I(resource_definition)
+ will override this option.
+ type: str
+ name:
+ description:
+ - Use to specify an object name.
+ - Use to create, delete, or discover an object without providing a full resource definition.
+ - Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a specific object.
+ - If I(resource definition) is provided, the I(metadata.name) value from the I(resource_definition)
+ will override this option.
+ type: str
+ namespace:
+ description:
+ - Use to specify an object namespace.
+ - Useful when creating, deleting, or discovering an object without providing a full resource definition.
+ - Use in conjunction with I(api_version), I(kind), and I(name) to identify a specific object.
+ - If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
+ will override this option.
+ type: str
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_resource_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_resource_options.py
new file mode 100644
index 00000000..6920efa4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_resource_options.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for providing an object configuration
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ resource_definition:
+ description:
+ - Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating.
+ - "NOTE: I(kind), I(api_version), I(name), and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)."
+ aliases:
+ - definition
+ - inline
+ src:
+ description:
+ - "Provide a path to a file containing a valid YAML definition of an object or objects to be created or updated. Mutually
+ exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(name), and I(namespace) will be
+ overwritten by corresponding values found in the configuration read in from the I(src) file."
+ - Reads from the local file system. To read from the Ansible controller's file system, including vaulted files, use the file lookup
+ plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
+ I(resource_definition). See Examples below.
+ - The URL to manifest files that can be used to create the resource. Added in version 2.4.0.
+ - Mutually exclusive with I(template) in case of M(kubernetes.core.k8s) module.
+ type: path
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_scale_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_scale_options.py
new file mode 100644
index 00000000..ca0605fd
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_scale_options.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options used by scale modules.
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ replicas:
+ description:
+ - The desired number of replicas.
+ type: int
+ required: True
+ current_replicas:
+ description:
+ - For Deployment, ReplicaSet, Replication Controller, only scale, if the number of existing replicas
+ matches. In the case of a Job, update parallelism only if the current parallelism value matches.
+ type: int
+ resource_version:
+ description:
+ - Only attempt to scale, if the current object version matches.
+ type: str
+ wait:
+ description:
+ - For Deployment, ReplicaSet, Replication Controller, wait for the status value of I(ready_replicas) to change
+ to the number of I(replicas). In the case of a Job, this option is ignored.
+ type: bool
+ default: yes
+ wait_timeout:
+ description:
+ - When C(wait) is I(True), the number of seconds to wait for the I(ready_replicas) status to equal I(replicas).
+ If the status is not reached within the allotted time, an error will result. In the case of a Job, this option
+ is ignored.
+ type: int
+ default: 20
+ wait_sleep:
+ description:
+ - Number of seconds to sleep between checks.
+ default: 5
+ type: int
+ version_added: 2.0.0
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_state_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_state_options.py
new file mode 100644
index 00000000..03331866
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_state_options.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for specifying object state
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ state:
+ description:
+ - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be
+ created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to
+ C(present), an existing object will be patched, if its attributes differ from those specified using
+ I(resource_definition) or I(src).
+ type: str
+ default: present
+ choices: [ absent, present ]
+ force:
+ description:
+ - If set to C(yes), and I(state) is C(present), an existing object will be replaced.
+ type: bool
+ default: no
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_wait_options.py b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_wait_options.py
new file mode 100644
index 00000000..e498e3ac
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/doc_fragments/k8s_wait_options.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# Options for specifying object wait
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r"""
+options:
+ wait:
+ description:
+ - Whether to wait for certain resource kinds to end up in the desired state.
+ - By default the module exits once Kubernetes has received the request.
+ - Implemented for C(state=present) for C(Deployment), C(DaemonSet) and C(Pod), and for C(state=absent) for all resource kinds.
+ - For resource kinds without an implementation, C(wait) returns immediately unless C(wait_condition) is set.
+ default: no
+ type: bool
+ wait_sleep:
+ description:
+ - Number of seconds to sleep between checks.
+ default: 5
+ type: int
+ wait_timeout:
+ description:
+ - How long in seconds to wait for the resource to end up in the desired state.
+ - Ignored if C(wait) is not set.
+ default: 120
+ type: int
+ wait_condition:
+ description:
+ - Specifies a custom condition on the status to wait for.
+ - Ignored if C(wait) is not set or is set to False.
+ suboptions:
+ type:
+ type: str
+ description:
+ - The type of condition to wait for.
+ - For example, the C(Pod) resource will set the C(Ready) condition (among others).
+ - Required if you are specifying a C(wait_condition).
+ - If left empty, the C(wait_condition) field will be ignored.
+ - The possible types for a condition are specific to each resource type in Kubernetes.
+ - See the API documentation of the status field for a given resource to see possible choices.
+ status:
+ type: str
+ description:
+ - The value of the status field in your desired condition.
+ - For example, if a C(Deployment) is paused, the C(Progressing) C(type) will have the C(Unknown) status.
+ choices:
+ - "True"
+ - "False"
+ - "Unknown"
+ default: "True"
+ reason:
+ type: str
+ description:
+ - The value of the reason field in your desired condition
+ - For example, if a C(Deployment) is paused, The C(Progressing) C(type) will have the C(DeploymentPaused) reason.
+ - The possible reasons in a condition are specific to each resource type in Kubernetes.
+ - See the API documentation of the status field for a given resource to see possible choices.
+ type: dict
+"""
diff --git a/ansible_collections/kubernetes/core/plugins/filter/k8s.py b/ansible_collections/kubernetes/core/plugins/filter/k8s.py
new file mode 100644
index 00000000..f5e0170e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/filter/k8s.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+from ansible.errors import AnsibleFilterError
+from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
+ generate_hash,
+)
+
+
+def k8s_config_resource_name(resource):
+ """
+ Generate resource name for the given resource of type ConfigMap, Secret
+ """
+ try:
+ return resource["metadata"]["name"] + "-" + generate_hash(resource)
+ except KeyError:
+ raise AnsibleFilterError(
+ "resource must have a metadata.name key to generate a resource name"
+ )
+
+
+# ---- Ansible filters ----
+class FilterModule(object):
+ def filters(self):
+ return {"k8s_config_resource_name": k8s_config_resource_name}
diff --git a/ansible_collections/kubernetes/core/plugins/filter/k8s_config_resource_name.yml b/ansible_collections/kubernetes/core/plugins/filter/k8s_config_resource_name.yml
new file mode 100644
index 00000000..c014ec25
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/filter/k8s_config_resource_name.yml
@@ -0,0 +1,36 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+DOCUMENTATION:
+ name: k8s_config_resource_name
+ short_description: Generate resource name for the given resource of type ConfigMap, Secret
+ description:
+ - Generate resource name for the given resource of type ConfigMap, Secret.
+ - Resource must have a C(metadata.name) key to generate a resource name
+ options:
+ _input:
+ description:
+ - A valid YAML definition for a ConfigMap or a Secret.
+ type: dict
+ required: true
+ author:
+ - ansible cloud team
+
+EXAMPLES: |
+ # Dump generated name for a configmap into a variable
+ - set_fact:
+ generated_name: '{{ definition | kubernetes.core.k8s_config_resource_name }}'
+ vars:
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: myconfigmap
+ namespace: mynamespace
+
+RETURN:
+ _value:
+ description: Generated resource name.
+ type: str
diff --git a/ansible_collections/kubernetes/core/plugins/inventory/k8s.py b/ansible_collections/kubernetes/core/plugins/inventory/k8s.py
new file mode 100644
index 00000000..099730f1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/inventory/k8s.py
@@ -0,0 +1,464 @@
+# Copyright (c) 2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+ name: k8s
+ author:
+ - Chris Houseknecht (@chouseknecht)
+ - Fabian von Feilitzsch (@fabianvf)
+
+ short_description: Kubernetes (K8s) inventory source
+
+ description:
+ - Fetch containers and services for one or more clusters.
+ - Groups by cluster name, namespace, namespace_services, namespace_pods, and labels.
+ - Uses the kubectl connection plugin to access the Kubernetes cluster.
+ - Uses k8s.(yml|yaml) YAML configuration file to set parameter values.
+
+ options:
+ plugin:
+ description: token that ensures this is a source file for the 'k8s' plugin.
+ required: True
+ choices: ['kubernetes.core.k8s', 'k8s', 'community.kubernetes.k8s']
+ connections:
+ description:
+ - Optional list of cluster connection settings. If no connections are provided, the default
+ I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces
+ the active user is authorized to access.
+ suboptions:
+ name:
+ description:
+ - Optional name to assign to the cluster. If not provided, a name is constructed from the server
+ and port.
+ kubeconfig:
+ description:
+ - 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 I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG
+ environment variable.
+ context:
+ description:
+ - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
+ variable.
+ host:
+ description:
+ - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
+ api_key:
+ description:
+ - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment
+ variable.
+ username:
+ description:
+ - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME
+ environment variable.
+ password:
+ description:
+ - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD
+ environment variable.
+ client_cert:
+ description:
+ - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
+ environment variable.
+ aliases: [ cert_file ]
+ client_key:
+ description:
+ - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE
+ environment variable.
+ aliases: [ key_file ]
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API. Can also be specified via
+ K8S_AUTH_SSL_CA_CERT environment variable.
+ aliases: [ ssl_ca_cert ]
+ validate_certs:
+ description:
+ - "Whether or not to verify the API server's SSL certificates. Can also be specified via
+ K8S_AUTH_VERIFY_SSL environment variable."
+ type: bool
+ aliases: [ verify_ssl ]
+ namespaces:
+ description:
+ - List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized
+ to access.
+
+ requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = """
+# File must be named k8s.yaml or k8s.yml
+
+# Authenticate with token, and return all pods and services for all namespaces
+plugin: kubernetes.core.k8s
+connections:
+ - host: https://192.168.64.4:8443
+ api_key: xxxxxxxxxxxxxxxx
+ validate_certs: false
+
+# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
+plugin: kubernetes.core.k8s
+connections:
+ - namespaces:
+ - testing
+
+# Use a custom config file, and a specific context.
+plugin: kubernetes.core.k8s
+connections:
+ - kubeconfig: /path/to/config
+ context: 'awx/192-168-64-4:8443/developer'
+"""
+
+import json
+
+from ansible.errors import AnsibleError
+from ansible_collections.kubernetes.core.plugins.module_utils.common import (
+ HAS_K8S_MODULE_HELPER,
+ k8s_import_exception,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
+
+try:
+ from kubernetes.dynamic.exceptions import DynamicApiError
+except ImportError:
+ pass
+
+
+def format_dynamic_api_exc(exc):
+ if exc.body:
+ if exc.headers and exc.headers.get("Content-Type") == "application/json":
+ message = json.loads(exc.body).get("message")
+ if message:
+ return message
+ return exc.body
+ else:
+ return "%s Reason: %s" % (exc.status, exc.reason)
+
+
+class K8sInventoryException(Exception):
+ pass
+
+
+class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
+ NAME = "kubernetes.core.k8s"
+
+ connection_plugin = "kubernetes.core.kubectl"
+ transport = "kubectl"
+
+ def parse(self, inventory, loader, path, cache=True):
+ super(InventoryModule, self).parse(inventory, loader, path)
+ cache_key = self._get_cache_prefix(path)
+ config_data = self._read_config_data(path)
+ self.setup(config_data, cache, cache_key)
+
+ def setup(self, config_data, cache, cache_key):
+ connections = config_data.get("connections")
+
+ if not HAS_K8S_MODULE_HELPER:
+ raise K8sInventoryException(
+ "This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
+ k8s_import_exception
+ )
+ )
+
+ source_data = None
+ if cache and cache_key in self._cache:
+ try:
+ source_data = self._cache[cache_key]
+ except KeyError:
+ pass
+
+ if not source_data:
+ self.fetch_objects(connections)
+
+ def fetch_objects(self, connections):
+
+ if connections:
+ if not isinstance(connections, list):
+ raise K8sInventoryException("Expecting connections to be a list.")
+
+ for connection in connections:
+ if not isinstance(connection, dict):
+ raise K8sInventoryException(
+ "Expecting connection to be a dictionary."
+ )
+ client = get_api_client(**connection)
+ name = connection.get(
+ "name", self.get_default_host_name(client.configuration.host)
+ )
+ if connection.get("namespaces"):
+ namespaces = connection["namespaces"]
+ else:
+ namespaces = self.get_available_namespaces(client)
+ for namespace in namespaces:
+ self.get_pods_for_namespace(client, name, namespace)
+ self.get_services_for_namespace(client, name, namespace)
+ else:
+ client = get_api_client()
+ name = self.get_default_host_name(client.configuration.host)
+ namespaces = self.get_available_namespaces(client)
+ for namespace in namespaces:
+ self.get_pods_for_namespace(client, name, namespace)
+ self.get_services_for_namespace(client, name, namespace)
+
+ @staticmethod
+ def get_default_host_name(host):
+ return (
+ host.replace("https://", "")
+ .replace("http://", "")
+ .replace(".", "-")
+ .replace(":", "_")
+ )
+
+ def get_available_namespaces(self, client):
+ v1_namespace = client.resources.get(api_version="v1", kind="Namespace")
+ try:
+ obj = v1_namespace.get()
+ except DynamicApiError as exc:
+ self.display.debug(exc)
+ raise K8sInventoryException(
+ "Error fetching Namespace list: %s" % format_dynamic_api_exc(exc)
+ )
+ return [namespace.metadata.name for namespace in obj.items]
+
+ def get_pods_for_namespace(self, client, name, namespace):
+ v1_pod = client.resources.get(api_version="v1", kind="Pod")
+ try:
+ obj = v1_pod.get(namespace=namespace)
+ except DynamicApiError as exc:
+ self.display.debug(exc)
+ raise K8sInventoryException(
+ "Error fetching Pod list: %s" % format_dynamic_api_exc(exc)
+ )
+
+ namespace_group = "namespace_{0}".format(namespace)
+ namespace_pods_group = "{0}_pods".format(namespace_group)
+
+ self.inventory.add_group(name)
+ self.inventory.add_group(namespace_group)
+ self.inventory.add_child(name, namespace_group)
+ self.inventory.add_group(namespace_pods_group)
+ self.inventory.add_child(namespace_group, namespace_pods_group)
+
+ for pod in obj.items:
+ pod_name = pod.metadata.name
+ pod_groups = []
+ pod_annotations = (
+ {} if not pod.metadata.annotations else dict(pod.metadata.annotations)
+ )
+
+ if pod.metadata.labels:
+ # create a group for each label_value
+ for key, value in pod.metadata.labels:
+ group_name = "label_{0}_{1}".format(key, value)
+ if group_name not in pod_groups:
+ pod_groups.append(group_name)
+ self.inventory.add_group(group_name)
+ pod_labels = dict(pod.metadata.labels)
+ else:
+ pod_labels = {}
+
+ if not pod.status.containerStatuses:
+ continue
+
+ for container in pod.status.containerStatuses:
+ # add each pod_container to the namespace group, and to each label_value group
+ container_name = "{0}_{1}".format(pod.metadata.name, container.name)
+ self.inventory.add_host(container_name)
+ self.inventory.add_child(namespace_pods_group, container_name)
+ if pod_groups:
+ for group in pod_groups:
+ self.inventory.add_child(group, container_name)
+
+ # Add hostvars
+ self.inventory.set_variable(container_name, "object_type", "pod")
+ self.inventory.set_variable(container_name, "labels", pod_labels)
+ self.inventory.set_variable(
+ container_name, "annotations", pod_annotations
+ )
+ self.inventory.set_variable(
+ container_name, "cluster_name", pod.metadata.clusterName
+ )
+ self.inventory.set_variable(
+ container_name, "pod_node_name", pod.spec.nodeName
+ )
+ self.inventory.set_variable(container_name, "pod_name", pod.spec.name)
+ self.inventory.set_variable(
+ container_name, "pod_host_ip", pod.status.hostIP
+ )
+ self.inventory.set_variable(
+ container_name, "pod_phase", pod.status.phase
+ )
+ self.inventory.set_variable(container_name, "pod_ip", pod.status.podIP)
+ self.inventory.set_variable(
+ container_name, "pod_self_link", pod.metadata.selfLink
+ )
+ self.inventory.set_variable(
+ container_name, "pod_resource_version", pod.metadata.resourceVersion
+ )
+ self.inventory.set_variable(container_name, "pod_uid", pod.metadata.uid)
+ self.inventory.set_variable(
+ container_name, "container_name", container.image
+ )
+ self.inventory.set_variable(
+ container_name, "container_image", container.image
+ )
+ if container.state.running:
+ self.inventory.set_variable(
+ container_name, "container_state", "Running"
+ )
+ if container.state.terminated:
+ self.inventory.set_variable(
+ container_name, "container_state", "Terminated"
+ )
+ if container.state.waiting:
+ self.inventory.set_variable(
+ container_name, "container_state", "Waiting"
+ )
+ self.inventory.set_variable(
+ container_name, "container_ready", container.ready
+ )
+ self.inventory.set_variable(
+ container_name, "ansible_remote_tmp", "/tmp/"
+ )
+ self.inventory.set_variable(
+ container_name, "ansible_connection", self.connection_plugin
+ )
+ self.inventory.set_variable(
+ container_name, "ansible_{0}_pod".format(self.transport), pod_name
+ )
+ self.inventory.set_variable(
+ container_name,
+ "ansible_{0}_container".format(self.transport),
+ container.name,
+ )
+ self.inventory.set_variable(
+ container_name,
+ "ansible_{0}_namespace".format(self.transport),
+ namespace,
+ )
+
+ def get_services_for_namespace(self, client, name, namespace):
+ v1_service = client.resources.get(api_version="v1", kind="Service")
+ try:
+ obj = v1_service.get(namespace=namespace)
+ except DynamicApiError as exc:
+ self.display.debug(exc)
+ raise K8sInventoryException(
+ "Error fetching Service list: %s" % format_dynamic_api_exc(exc)
+ )
+
+ namespace_group = "namespace_{0}".format(namespace)
+ namespace_services_group = "{0}_services".format(namespace_group)
+
+ self.inventory.add_group(name)
+ self.inventory.add_group(namespace_group)
+ self.inventory.add_child(name, namespace_group)
+ self.inventory.add_group(namespace_services_group)
+ self.inventory.add_child(namespace_group, namespace_services_group)
+
+ for service in obj.items:
+ service_name = service.metadata.name
+ service_labels = (
+ {} if not service.metadata.labels else dict(service.metadata.labels)
+ )
+ service_annotations = (
+ {}
+ if not service.metadata.annotations
+ else dict(service.metadata.annotations)
+ )
+
+ self.inventory.add_host(service_name)
+
+ if service.metadata.labels:
+ # create a group for each label_value
+ for key, value in service.metadata.labels:
+ group_name = "label_{0}_{1}".format(key, value)
+ self.inventory.add_group(group_name)
+ self.inventory.add_child(group_name, service_name)
+
+ try:
+ self.inventory.add_child(namespace_services_group, service_name)
+ except AnsibleError:
+ raise
+
+ ports = [
+ {
+ "name": port.name,
+ "port": port.port,
+ "protocol": port.protocol,
+ "targetPort": port.targetPort,
+ "nodePort": port.nodePort,
+ }
+ for port in service.spec.ports or []
+ ]
+
+ # add hostvars
+ self.inventory.set_variable(service_name, "object_type", "service")
+ self.inventory.set_variable(service_name, "labels", service_labels)
+ self.inventory.set_variable(
+ service_name, "annotations", service_annotations
+ )
+ self.inventory.set_variable(
+ service_name, "cluster_name", service.metadata.clusterName
+ )
+ self.inventory.set_variable(service_name, "ports", ports)
+ self.inventory.set_variable(service_name, "type", service.spec.type)
+ self.inventory.set_variable(
+ service_name, "self_link", service.metadata.selfLink
+ )
+ self.inventory.set_variable(
+ service_name, "resource_version", service.metadata.resourceVersion
+ )
+ self.inventory.set_variable(service_name, "uid", service.metadata.uid)
+
+ if service.spec.externalTrafficPolicy:
+ self.inventory.set_variable(
+ service_name,
+ "external_traffic_policy",
+ service.spec.externalTrafficPolicy,
+ )
+ if service.spec.externalIPs:
+ self.inventory.set_variable(
+ service_name, "external_ips", service.spec.externalIPs
+ )
+
+ if service.spec.externalName:
+ self.inventory.set_variable(
+ service_name, "external_name", service.spec.externalName
+ )
+
+ if service.spec.healthCheckNodePort:
+ self.inventory.set_variable(
+ service_name,
+ "health_check_node_port",
+ service.spec.healthCheckNodePort,
+ )
+ if service.spec.loadBalancerIP:
+ self.inventory.set_variable(
+ service_name, "load_balancer_ip", service.spec.loadBalancerIP
+ )
+ if service.spec.selector:
+ self.inventory.set_variable(
+ service_name, "selector", dict(service.spec.selector)
+ )
+
+ if (
+ hasattr(service.status.loadBalancer, "ingress")
+ and service.status.loadBalancer.ingress
+ ):
+ load_balancer = [
+ {"hostname": ingress.hostname, "ip": ingress.ip}
+ for ingress in service.status.loadBalancer.ingress
+ ]
+ self.inventory.set_variable(
+ service_name, "load_balancer", load_balancer
+ )
diff --git a/ansible_collections/kubernetes/core/plugins/lookup/k8s.py b/ansible_collections/kubernetes/core/plugins/lookup/k8s.py
new file mode 100644
index 00000000..bd69a992
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/lookup/k8s.py
@@ -0,0 +1,304 @@
+#
+# Copyright 2018 Red Hat | Ansible
+#
+# 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 = """
+ name: k8s
+
+ short_description: Query the K8s API
+
+ author:
+ - Chris Houseknecht (@chouseknecht)
+ - Fabian von Feilitzsch (@fabianvf)
+
+ description:
+ - Uses the Kubernetes Python client to fetch a specific object by name, all matching objects within a
+ namespace, or all matching objects for all namespaces, as well as information about the cluster.
+ - Provides access the full range of K8s APIs.
+ - Enables authentication via config file, certificates, password or token.
+ notes:
+ - While querying, please use C(query) or C(lookup) format with C(wantlist=True) to provide an easier and more
+ consistent interface. For more details, see
+ U(https://docs.ansible.com/ansible/latest/plugins/lookup.html#forcing-lookups-to-return-lists-query-and-wantlist-true).
+ options:
+ cluster_info:
+ description:
+ - Use to specify the type of cluster information you are attempting to retrieve. Will take priority
+ over all the other options.
+ api_version:
+ description:
+ - Use to specify the API version. If I(resource definition) is provided, the I(apiVersion) from the
+ I(resource_definition) will override this option.
+ default: v1
+ kind:
+ description:
+ - Use to specify an object model. If I(resource definition) is provided, the I(kind) from a
+ I(resource_definition) will override this option.
+ required: true
+ resource_name:
+ description:
+ - Fetch a specific object by name. If I(resource definition) is provided, the I(metadata.name) value
+ from the I(resource_definition) will override this option.
+ namespace:
+ description:
+ - Limit the objects returned to a specific namespace. If I(resource definition) is provided, the
+ I(metadata.namespace) value from the I(resource_definition) will override this option.
+ label_selector:
+ description:
+ - Additional labels to include in the query. Ignored when I(resource_name) is provided.
+ field_selector:
+ description:
+ - Specific fields on which to query. Ignored when I(resource_name) is provided.
+ resource_definition:
+ description:
+ - "Provide a YAML configuration for an object. NOTE: I(kind), I(api_version), I(resource_name),
+ and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)."
+ src:
+ description:
+ - "Provide a path to a file containing a valid YAML definition of an object dated. Mutually
+ exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(resource_name), and I(namespace)
+ will be overwritten by corresponding values found in the configuration read in from the I(src) file."
+ - Reads from the local file system. To read from the Ansible controller's file system, 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.
+ host:
+ description:
+ - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
+ api_key:
+ description:
+ - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable.
+ kubeconfig:
+ description:
+ - 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 I(~/.kube/config). Can also be specified via K8S_AUTH_KUBECONFIG environment
+ variable.
+ context:
+ description:
+ - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
+ variable.
+ username:
+ description:
+ - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment
+ variable.
+ password:
+ description:
+ - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment
+ variable.
+ client_cert:
+ description:
+ - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
+ environment
+ variable.
+ aliases: [ cert_file ]
+ client_key:
+ description:
+ - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment
+ variable.
+ aliases: [ key_file ]
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API. Can also be specified via K8S_AUTH_SSL_CA_CERT
+ environment variable.
+ aliases: [ ssl_ca_cert ]
+ validate_certs:
+ description:
+ - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL
+ environment variable.
+ type: bool
+ aliases: [ verify_ssl ]
+
+ requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = """
+- name: Fetch a list of namespaces
+ set_fact:
+ projects: "{{ query('kubernetes.core.k8s', api_version='v1', kind='Namespace') }}"
+
+- name: Fetch all deployments
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment') }}"
+
+- name: Fetch all deployments in a namespace
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing') }}"
+
+- name: Fetch a specific deployment by name
+ set_fact:
+ deployments: "{{ query('kubernetes.core.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
+
+- name: Fetch with label selector
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', kind='Service', label_selector='app=galaxy') }}"
+
+# Use parameters from a YAML config
+
+- name: Load config from the Ansible controller filesystem
+ set_fact:
+ config: "{{ lookup('file', 'service.yml') | from_yaml }}"
+
+- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', resource_definition=config) }}"
+
+- name: Use a config from the local filesystem
+ set_fact:
+ service: "{{ query('kubernetes.core.k8s', src='service.yml') }}"
+"""
+
+RETURN = """
+ _list:
+ description:
+ - One ore more object definitions returned from the API.
+ type: list
+ elements: dict
+ sample:
+ - kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ creationTimestamp: "2022-03-04T13:59:49Z"
+ name: my-config-map
+ namespace: default
+ resourceVersion: "418"
+ uid: 5714b011-d090-4eac-8272-a0ea82ec0abd
+ data:
+ key1: val1
+"""
+
+import os
+
+from ansible.errors import AnsibleError
+from ansible.module_utils.common._collections_compat import KeysView
+from ansible.module_utils.common.validation import check_type_bool
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
+ create_definitions,
+)
+
+try:
+ enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
+except TypeError:
+ enable_turbo_mode = False
+
+if enable_turbo_mode:
+ try:
+ from ansible_collections.cloud.common.plugins.plugin_utils.turbo.lookup import (
+ TurboLookupBase as LookupBase,
+ )
+ except ImportError:
+ from ansible.plugins.lookup import LookupBase # noqa: F401
+else:
+ from ansible.plugins.lookup import LookupBase # noqa: F401
+
+try:
+ from kubernetes.dynamic.exceptions import NotFoundError
+
+ HAS_K8S_MODULE_HELPER = True
+ k8s_import_exception = None
+except ImportError as e:
+ HAS_K8S_MODULE_HELPER = False
+ k8s_import_exception = e
+
+
+class KubernetesLookup(object):
+ def __init__(self):
+
+ if not HAS_K8S_MODULE_HELPER:
+ raise Exception(
+ "Requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: {0}".format(
+ k8s_import_exception
+ )
+ )
+
+ self.kind = None
+ self.name = None
+ self.namespace = None
+ self.api_version = None
+ self.label_selector = None
+ self.field_selector = None
+ self.include_uninitialized = None
+ self.resource_definition = None
+ self.helper = None
+ self.connection = {}
+
+ def fail(self, msg=None):
+ raise AnsibleError(msg)
+
+ def run(self, terms, variables=None, **kwargs):
+ self.params = kwargs
+ self.client = get_api_client(**kwargs)
+
+ cluster_info = kwargs.get("cluster_info")
+ if cluster_info == "version":
+ return [self.client.client.version]
+ if cluster_info == "api_groups":
+ if isinstance(self.client.resources.api_groups, KeysView):
+ return [list(self.client.resources.api_groups)]
+ return [self.client.resources.api_groups]
+
+ self.kind = kwargs.get("kind")
+ self.name = kwargs.get("resource_name")
+ self.namespace = kwargs.get("namespace")
+ self.api_version = kwargs.get("api_version", "v1")
+ self.label_selector = kwargs.get("label_selector")
+ self.field_selector = kwargs.get("field_selector")
+ self.include_uninitialized = kwargs.get("include_uninitialized", False)
+
+ resource_definition = kwargs.get("resource_definition")
+ src = kwargs.get("src")
+ if src:
+ definitions = create_definitions(params=dict(src=src))
+ if definitions:
+ self.kind = definitions[0].kind
+ self.name = definitions[0].name
+ self.namespace = definitions[0].namespace
+ self.api_version = definitions[0].api_version or "v1"
+ if resource_definition:
+ self.kind = resource_definition.get("kind", self.kind)
+ self.api_version = resource_definition.get("apiVersion", self.api_version)
+ self.name = resource_definition.get("metadata", {}).get("name", self.name)
+ self.namespace = resource_definition.get("metadata", {}).get(
+ "namespace", self.namespace
+ )
+
+ if not self.kind:
+ raise AnsibleError(
+ "Error: no Kind specified. Use the 'kind' parameter, or provide an object YAML configuration "
+ "using the 'resource_definition' parameter."
+ )
+
+ resource = self.client.resource(self.kind, self.api_version)
+ try:
+ params = dict(
+ name=self.name,
+ namespace=self.namespace,
+ label_selector=self.label_selector,
+ field_selector=self.field_selector,
+ )
+ k8s_obj = self.client.get(resource, **params)
+ except NotFoundError:
+ return []
+
+ if self.name:
+ return [k8s_obj.to_dict()]
+
+ return k8s_obj.to_dict().get("items")
+
+
+class LookupModule(LookupBase):
+ def _run(self, terms, variables=None, **kwargs):
+ return KubernetesLookup().run(terms, variables=variables, **kwargs)
+
+ run = _run if not hasattr(LookupBase, "run_on_daemon") else LookupBase.run_on_daemon
diff --git a/ansible_collections/kubernetes/core/plugins/lookup/kustomize.py b/ansible_collections/kubernetes/core/plugins/lookup/kustomize.py
new file mode 100644
index 00000000..ef7e50f0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/lookup/kustomize.py
@@ -0,0 +1,131 @@
+#
+# Copyright 2021 Red Hat | Ansible
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+DOCUMENTATION = """
+ name: kustomize
+
+ short_description: Build a set of kubernetes resources using a 'kustomization.yaml' file.
+
+ version_added: "2.2.0"
+
+ author:
+ - Aubin Bikouo (@abikouo)
+ notes:
+ - If both kustomize and kubectl are part of the PATH, kustomize will be used by the plugin.
+ description:
+ - Uses the kustomize or the kubectl tool.
+ - Return the result of C(kustomize build) or C(kubectl kustomize).
+ options:
+ dir:
+ description:
+ - The directory path containing 'kustomization.yaml',
+ or a git repository URL with a path suffix specifying same with respect to the repository root.
+ - If omitted, '.' is assumed.
+ default: "."
+ binary_path:
+ description:
+ - The path of a kustomize or kubectl binary to use.
+ opt_dirs:
+ description:
+ - An optional list of directories to search for the executable in addition to PATH.
+
+ requirements:
+ - "python >= 3.6"
+"""
+
+EXAMPLES = """
+- name: Run lookup using kustomize
+ set_fact:
+ resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kustomize') }}"
+
+- name: Run lookup using kubectl kustomize
+ set_fact:
+ resources: "{{ lookup('kubernetes.core.kustomize', binary_path='/path/to/kubectl') }}"
+
+- name: Create kubernetes resources for lookup output
+ k8s:
+ definition: "{{ lookup('kubernetes.core.kustomize', dir='/path/to/kustomization') }}"
+"""
+
+RETURN = """
+ _list:
+ description:
+ - YAML string for the object definitions returned from the tool execution.
+ type: str
+ sample:
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: my-config-map
+ namespace: default
+ data:
+ key1: val1
+"""
+
+from ansible.errors import AnsibleLookupError
+from ansible.plugins.lookup import LookupBase
+from ansible.module_utils.common.process import get_bin_path
+
+
+import subprocess
+
+
+def get_binary_from_path(name, opt_dirs=None):
+ opt_arg = {}
+ try:
+ if opt_dirs is not None:
+ if not isinstance(opt_dirs, list):
+ opt_dirs = [opt_dirs]
+ opt_arg["opt_dirs"] = opt_dirs
+ bin_path = get_bin_path(name, **opt_arg)
+ return bin_path
+ except ValueError:
+ return None
+
+
+def run_command(command):
+ cmd = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return cmd.communicate()
+
+
+class LookupModule(LookupBase):
+ def run(
+ self, terms, variables=None, dir=".", binary_path=None, opt_dirs=None, **kwargs
+ ):
+ executable_path = binary_path
+ if executable_path is None:
+ executable_path = get_binary_from_path(name="kustomize", opt_dirs=opt_dirs)
+ if executable_path is None:
+ executable_path = get_binary_from_path(
+ name="kubectl", opt_dirs=opt_dirs
+ )
+
+ # validate that at least one tool was found
+ if executable_path is None:
+ raise AnsibleLookupError(
+ "Failed to find required executable 'kubectl' and 'kustomize' in paths"
+ )
+
+ # check input directory
+ kustomization_dir = dir
+
+ command = [executable_path]
+ if executable_path.endswith("kustomize"):
+ command += ["build", kustomization_dir]
+ elif executable_path.endswith("kubectl"):
+ command += ["kustomize", kustomization_dir]
+ else:
+ raise AnsibleLookupError(
+ "unexpected tool provided as parameter {0}, expected one of kustomize, kubectl.".format(
+ executable_path
+ )
+ )
+
+ (out, err) = run_command(command)
+ if err:
+ raise AnsibleLookupError(
+ "kustomize command failed with: {0}".format(err.decode("utf-8"))
+ )
+ return [out.decode("utf-8")]
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/__init__.py b/ansible_collections/kubernetes/core/plugins/module_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/__init__.py
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/_version.py b/ansible_collections/kubernetes/core/plugins/module_utils/_version.py
new file mode 100644
index 00000000..d91cf3ab
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/_version.py
@@ -0,0 +1,344 @@
+# Vendored copy of distutils/version.py from CPython 3.9.5
+#
+# Implements multiple version numbering conventions for the
+# Python Module Distribution Utilities.
+#
+# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
+#
+
+"""Provides classes to represent module version numbers (one class for
+each style of version numbering). There are currently two such classes
+implemented: StrictVersion and LooseVersion.
+
+Every version number class implements the following interface:
+ * the 'parse' method takes a string and parses it to some internal
+ representation; if the string is an invalid version number,
+ 'parse' raises a ValueError exception
+ * the class constructor takes an optional string argument which,
+ if supplied, is passed to 'parse'
+ * __str__ reconstructs the string that was passed to 'parse' (or
+ an equivalent string -- ie. one that will generate an equivalent
+ version number instance)
+ * __repr__ generates Python code to recreate the version number instance
+ * _cmp compares the current instance with either another instance
+ of the same class or a string (which will be parsed to an instance
+ of the same class, thus must follow the same rules)
+"""
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import re
+
+try:
+ RE_FLAGS = re.VERBOSE | re.ASCII
+except AttributeError:
+ RE_FLAGS = re.VERBOSE
+
+
+class Version:
+ """Abstract base class for version numbering classes. Just provides
+ constructor (__init__) and reproducer (__repr__), because those
+ seem to be the same for all version numbering classes; and route
+ rich comparisons to _cmp.
+ """
+
+ def __init__(self, vstring=None):
+ if vstring:
+ self.parse(vstring)
+
+ def __repr__(self):
+ return "%s ('%s')" % (self.__class__.__name__, str(self))
+
+ def __eq__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c == 0
+
+ def __lt__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c < 0
+
+ def __le__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c <= 0
+
+ def __gt__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c > 0
+
+ def __ge__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c >= 0
+
+
+# Interface for version-number classes -- must be implemented
+# by the following classes (the concrete ones -- Version should
+# be treated as an abstract class).
+# __init__ (string) - create and take same action as 'parse'
+# (string parameter is optional)
+# parse (string) - convert a string representation to whatever
+# internal representation is appropriate for
+# this style of version numbering
+# __str__ (self) - convert back to a string; should be very similar
+# (if not identical to) the string supplied to parse
+# __repr__ (self) - generate Python code to recreate
+# the instance
+# _cmp (self, other) - compare two version numbers ('other' may
+# be an unparsed version string, or another
+# instance of your version class)
+
+
+class StrictVersion(Version):
+ """Version numbering for anal retentives and software idealists.
+ Implements the standard interface for version number classes as
+ described above. A version number consists of two or three
+ dot-separated numeric components, with an optional "pre-release" tag
+ on the end. The pre-release tag consists of the letter 'a' or 'b'
+ followed by a number. If the numeric components of two version
+ numbers are equal, then one with a pre-release tag will always
+ be deemed earlier (lesser) than one without.
+
+ The following are valid version numbers (shown in the order that
+ would be obtained by sorting according to the supplied cmp function):
+
+ 0.4 0.4.0 (these two are equivalent)
+ 0.4.1
+ 0.5a1
+ 0.5b3
+ 0.5
+ 0.9.6
+ 1.0
+ 1.0.4a3
+ 1.0.4b1
+ 1.0.4
+
+ The following are examples of invalid version numbers:
+
+ 1
+ 2.7.2.2
+ 1.3.a4
+ 1.3pl1
+ 1.3c4
+
+ The rationale for this version numbering system will be explained
+ in the distutils documentation.
+ """
+
+ version_re = re.compile(r"^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$", RE_FLAGS)
+
+ def parse(self, vstring):
+ match = self.version_re.match(vstring)
+ if not match:
+ raise ValueError("invalid version number '%s'" % vstring)
+
+ (major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
+
+ if patch:
+ self.version = tuple(map(int, [major, minor, patch]))
+ else:
+ self.version = tuple(map(int, [major, minor])) + (0,)
+
+ if prerelease:
+ self.prerelease = (prerelease[0], int(prerelease_num))
+ else:
+ self.prerelease = None
+
+ def __str__(self):
+ if self.version[2] == 0:
+ vstring = ".".join(map(str, self.version[0:2]))
+ else:
+ vstring = ".".join(map(str, self.version))
+
+ if self.prerelease:
+ vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
+
+ return vstring
+
+ def _cmp(self, other):
+ if isinstance(other, str):
+ other = StrictVersion(other)
+ elif not isinstance(other, StrictVersion):
+ return NotImplemented
+
+ if self.version != other.version:
+ # numeric versions don't match
+ # prerelease stuff doesn't matter
+ if self.version < other.version:
+ return -1
+ else:
+ return 1
+
+ # have to compare prerelease
+ # case 1: neither has prerelease; they're equal
+ # case 2: self has prerelease, other doesn't; other is greater
+ # case 3: self doesn't have prerelease, other does: self is greater
+ # case 4: both have prerelease: must compare them!
+
+ if not self.prerelease and not other.prerelease:
+ return 0
+ elif self.prerelease and not other.prerelease:
+ return -1
+ elif not self.prerelease and other.prerelease:
+ return 1
+ elif self.prerelease and other.prerelease:
+ if self.prerelease == other.prerelease:
+ return 0
+ elif self.prerelease < other.prerelease:
+ return -1
+ else:
+ return 1
+ else:
+ raise AssertionError("never get here")
+
+
+# end class StrictVersion
+
+# The rules according to Greg Stein:
+# 1) a version number has 1 or more numbers separated by a period or by
+# sequences of letters. If only periods, then these are compared
+# left-to-right to determine an ordering.
+# 2) sequences of letters are part of the tuple for comparison and are
+# compared lexicographically
+# 3) recognize the numeric components may have leading zeroes
+#
+# The LooseVersion class below implements these rules: a version number
+# string is split up into a tuple of integer and string components, and
+# comparison is a simple tuple comparison. This means that version
+# numbers behave in a predictable and obvious way, but a way that might
+# not necessarily be how people *want* version numbers to behave. There
+# wouldn't be a problem if people could stick to purely numeric version
+# numbers: just split on period and compare the numbers as tuples.
+# However, people insist on putting letters into their version numbers;
+# the most common purpose seems to be:
+# - indicating a "pre-release" version
+# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
+# - indicating a post-release patch ('p', 'pl', 'patch')
+# but of course this can't cover all version number schemes, and there's
+# no way to know what a programmer means without asking him.
+#
+# The problem is what to do with letters (and other non-numeric
+# characters) in a version number. The current implementation does the
+# obvious and predictable thing: keep them as strings and compare
+# lexically within a tuple comparison. This has the desired effect if
+# an appended letter sequence implies something "post-release":
+# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
+#
+# However, if letters in a version number imply a pre-release version,
+# the "obvious" thing isn't correct. Eg. you would expect that
+# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
+# implemented here, this just isn't so.
+#
+# Two possible solutions come to mind. The first is to tie the
+# comparison algorithm to a particular set of semantic rules, as has
+# been done in the StrictVersion class above. This works great as long
+# as everyone can go along with bondage and discipline. Hopefully a
+# (large) subset of Python module programmers will agree that the
+# particular flavour of bondage and discipline provided by StrictVersion
+# provides enough benefit to be worth using, and will submit their
+# version numbering scheme to its domination. The free-thinking
+# anarchists in the lot will never give in, though, and something needs
+# to be done to accommodate them.
+#
+# Perhaps a "moderately strict" version class could be implemented that
+# lets almost anything slide (syntactically), and makes some heuristic
+# assumptions about non-digits in version number strings. This could
+# sink into special-case-hell, though; if I was as talented and
+# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
+# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
+# just as happy dealing with things like "2g6" and "1.13++". I don't
+# think I'm smart enough to do it right though.
+#
+# In any case, I've coded the test suite for this module (see
+# ../test/test_version.py) specifically to fail on things like comparing
+# "1.2a2" and "1.2". That's not because the *code* is doing anything
+# wrong, it's because the simple, obvious design doesn't match my
+# complicated, hairy expectations for real-world version numbers. It
+# would be a snap to fix the test suite to say, "Yep, LooseVersion does
+# the Right Thing" (ie. the code matches the conception). But I'd rather
+# have a conception that matches common notions about version numbers.
+
+
+class LooseVersion(Version):
+ """Version numbering for anarchists and software realists.
+ Implements the standard interface for version number classes as
+ described above. A version number consists of a series of numbers,
+ separated by either periods or strings of letters. When comparing
+ version numbers, the numeric components will be compared
+ numerically, and the alphabetic components lexically. The following
+ are all valid version numbers, in no particular order:
+
+ 1.5.1
+ 1.5.2b2
+ 161
+ 3.10a
+ 8.02
+ 3.4j
+ 1996.07.12
+ 3.2.pl0
+ 3.1.1.6
+ 2g6
+ 11g
+ 0.960923
+ 2.2beta29
+ 1.13++
+ 5.5.kw
+ 2.0b1pl0
+
+ In fact, there is no such thing as an invalid version number under
+ this scheme; the rules for comparison are simple and predictable,
+ but may not always give the results you want (for some definition
+ of "want").
+ """
+
+ component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
+
+ def __init__(self, vstring=None):
+ if vstring:
+ self.parse(vstring)
+
+ def parse(self, vstring):
+ # I've given up on thinking I can reconstruct the version string
+ # from the parsed tuple -- so I just store the string here for
+ # use by __str__
+ self.vstring = vstring
+ components = [x for x in self.component_re.split(vstring) if x and x != "."]
+ for i, obj in enumerate(components):
+ try:
+ components[i] = int(obj)
+ except ValueError:
+ pass
+
+ self.version = components
+
+ def __str__(self):
+ return self.vstring
+
+ def __repr__(self):
+ return "LooseVersion ('%s')" % str(self)
+
+ def _cmp(self, other):
+ if isinstance(other, str):
+ other = LooseVersion(other)
+ elif not isinstance(other, LooseVersion):
+ return NotImplemented
+
+ if self.version == other.version:
+ return 0
+ if self.version < other.version:
+ return -1
+ if self.version > other.version:
+ return 1
+
+
+# end class LooseVersion
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/ansiblemodule.py b/ansible_collections/kubernetes/core/plugins/module_utils/ansiblemodule.py
new file mode 100644
index 00000000..8b17866d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/ansiblemodule.py
@@ -0,0 +1,25 @@
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+import os
+
+from ansible.module_utils.common.validation import check_type_bool
+
+try:
+ enable_turbo_mode = check_type_bool(os.environ.get("ENABLE_TURBO_MODE"))
+except TypeError:
+ enable_turbo_mode = False
+
+if enable_turbo_mode:
+ try:
+ from ansible_collections.cloud.common.plugins.module_utils.turbo.module import (
+ 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
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/apply.py b/ansible_collections/kubernetes/core/plugins/module_utils/apply.py
new file mode 100644
index 00000000..dea185ef
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/apply.py
@@ -0,0 +1,316 @@
+# Copyright [2017] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from collections import OrderedDict
+import json
+
+from ansible.module_utils.common.dict_transformations import dict_merge
+from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
+ ApplyException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ gather_versions,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+
+try:
+ from kubernetes.dynamic.exceptions import NotFoundError
+except ImportError:
+ pass
+
+
+LAST_APPLIED_CONFIG_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
+
+POD_SPEC_SUFFIXES = {
+ "containers": "name",
+ "initContainers": "name",
+ "ephemeralContainers": "name",
+ "volumes": "name",
+ "imagePullSecrets": "name",
+ "containers.volumeMounts": "mountPath",
+ "containers.volumeDevices": "devicePath",
+ "containers.env": "name",
+ "containers.ports": "containerPort",
+ "initContainers.volumeMounts": "mountPath",
+ "initContainers.volumeDevices": "devicePath",
+ "initContainers.env": "name",
+ "initContainers.ports": "containerPort",
+ "ephemeralContainers.volumeMounts": "mountPath",
+ "ephemeralContainers.volumeDevices": "devicePath",
+ "ephemeralContainers.env": "name",
+ "ephemeralContainers.ports": "containerPort",
+}
+
+POD_SPEC_PREFIXES = [
+ "Pod.spec",
+ "Deployment.spec.template.spec",
+ "DaemonSet.spec.template.spec",
+ "StatefulSet.spec.template.spec",
+ "Job.spec.template.spec",
+ "Cronjob.spec.jobTemplate.spec.template.spec",
+]
+
+# patch merge keys taken from generated.proto files under
+# staging/src/k8s.io/api in kubernetes/kubernetes
+STRATEGIC_MERGE_PATCH_KEYS = {
+ "Service.spec.ports": "port",
+ "ServiceAccount.secrets": "name",
+ "ValidatingWebhookConfiguration.webhooks": "name",
+ "MutatingWebhookConfiguration.webhooks": "name",
+}
+
+STRATEGIC_MERGE_PATCH_KEYS.update(
+ {
+ "%s.%s" % (prefix, key): value
+ for prefix in POD_SPEC_PREFIXES
+ for key, value in POD_SPEC_SUFFIXES.items()
+ }
+)
+
+
+def annotate(desired):
+ return dict(
+ metadata=dict(
+ annotations={
+ LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(
+ desired, separators=(",", ":"), indent=None, sort_keys=True
+ )
+ }
+ )
+ )
+
+
+def apply_patch(actual, desired):
+ last_applied = (
+ actual["metadata"].get("annotations", {}).get(LAST_APPLIED_CONFIG_ANNOTATION)
+ )
+
+ if last_applied:
+ # ensure that last_applied doesn't come back as a dict of unicode key/value pairs
+ # json.loads can be used if we stop supporting python 2
+ last_applied = json.loads(last_applied)
+ patch = merge(
+ dict_merge(last_applied, annotate(last_applied)),
+ dict_merge(desired, annotate(desired)),
+ actual,
+ )
+ if patch:
+ return actual, patch
+ else:
+ return actual, actual
+ else:
+ return actual, dict_merge(desired, annotate(desired))
+
+
+def apply_object(resource, definition, server_side=False):
+ try:
+ actual = resource.get(
+ name=definition["metadata"]["name"],
+ namespace=definition["metadata"].get("namespace"),
+ )
+ if server_side:
+ return actual, None
+ except NotFoundError:
+ return None, dict_merge(definition, annotate(definition))
+ return apply_patch(actual.to_dict(), definition)
+
+
+def k8s_apply(resource, definition, **kwargs):
+ existing, desired = apply_object(resource, definition)
+ server_side = kwargs.get("server_side", False)
+ if server_side:
+ versions = gather_versions()
+ body = definition
+ if LooseVersion(versions["kubernetes"]) < LooseVersion("25.0.0"):
+ body = json.dumps(definition).encode()
+ # server_side_apply is forces content_type to 'application/apply-patch+yaml'
+ return resource.server_side_apply(
+ body=body,
+ name=definition["metadata"]["name"],
+ namespace=definition["metadata"].get("namespace"),
+ force_conflicts=kwargs.get("force_conflicts"),
+ field_manager=kwargs.get("field_manager"),
+ dry_run=kwargs.get("dry_run"),
+ )
+ if not existing:
+ return resource.create(
+ body=desired, namespace=definition["metadata"].get("namespace"), **kwargs
+ )
+ if existing == desired:
+ return resource.get(
+ name=definition["metadata"]["name"],
+ namespace=definition["metadata"].get("namespace"),
+ )
+ return resource.patch(
+ body=desired,
+ name=definition["metadata"]["name"],
+ namespace=definition["metadata"].get("namespace"),
+ content_type="application/merge-patch+json",
+ **kwargs
+ )
+
+
+# The patch is the difference from actual to desired without deletions, plus deletions
+# from last_applied to desired. To find it, we compute deletions, which are the deletions from
+# last_applied to desired, and delta, which is the difference from actual to desired without
+# deletions, and then apply delta to deletions as a patch, which should be strictly additive.
+def merge(last_applied, desired, actual, position=None):
+ deletions = get_deletions(last_applied, desired)
+ delta = get_delta(last_applied, actual, desired, position or desired["kind"])
+ return dict_merge(deletions, delta)
+
+
+def list_to_dict(lst, key, position):
+ result = OrderedDict()
+ for item in lst:
+ try:
+ result[item[key]] = item
+ except KeyError:
+ raise ApplyException(
+ "Expected key '%s' not found in position %s" % (key, position)
+ )
+ return result
+
+
+# list_merge applies a strategic merge to a set of lists if the patchMergeKey is known
+# each item in the list is compared based on the patchMergeKey - if two values with the
+# same patchMergeKey differ, we take the keys that are in last applied, compare the
+# actual and desired for those keys, and update if any differ
+def list_merge(last_applied, actual, desired, position):
+ result = list()
+ if position in STRATEGIC_MERGE_PATCH_KEYS and last_applied:
+ patch_merge_key = STRATEGIC_MERGE_PATCH_KEYS[position]
+ last_applied_dict = list_to_dict(last_applied, patch_merge_key, position)
+ actual_dict = list_to_dict(actual, patch_merge_key, position)
+ desired_dict = list_to_dict(desired, patch_merge_key, position)
+ for key in desired_dict:
+ if key not in actual_dict or key not in last_applied_dict:
+ result.append(desired_dict[key])
+ else:
+ patch = merge(
+ last_applied_dict[key],
+ desired_dict[key],
+ actual_dict[key],
+ position,
+ )
+ result.append(dict_merge(actual_dict[key], patch))
+ for key in actual_dict:
+ if key not in desired_dict and key not in last_applied_dict:
+ result.append(actual_dict[key])
+ return result
+ else:
+ return desired
+
+
+def recursive_list_diff(list1, list2, position=None):
+ result = (list(), list())
+ if position in STRATEGIC_MERGE_PATCH_KEYS:
+ patch_merge_key = STRATEGIC_MERGE_PATCH_KEYS[position]
+ dict1 = list_to_dict(list1, patch_merge_key, position)
+ dict2 = list_to_dict(list2, patch_merge_key, position)
+ dict1_keys = set(dict1.keys())
+ dict2_keys = set(dict2.keys())
+ for key in dict1_keys - dict2_keys:
+ result[0].append(dict1[key])
+ for key in dict2_keys - dict1_keys:
+ result[1].append(dict2[key])
+ for key in dict1_keys & dict2_keys:
+ diff = recursive_diff(dict1[key], dict2[key], position)
+ if diff:
+ # reinsert patch merge key to relate changes in other keys to
+ # a specific list element
+ diff[0].update({patch_merge_key: dict1[key][patch_merge_key]})
+ diff[1].update({patch_merge_key: dict2[key][patch_merge_key]})
+ result[0].append(diff[0])
+ result[1].append(diff[1])
+ if result[0] or result[1]:
+ return result
+ elif list1 != list2:
+ return (list1, list2)
+ return None
+
+
+def recursive_diff(dict1, dict2, position=None):
+ if not position:
+ if "kind" in dict1 and dict1.get("kind") == dict2.get("kind"):
+ position = dict1["kind"]
+ left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
+ right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
+ for k in set(dict1.keys()) & set(dict2.keys()):
+ if position:
+ this_position = "%s.%s" % (position, k)
+ if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
+ result = recursive_diff(dict1[k], dict2[k], this_position)
+ if result:
+ left[k] = result[0]
+ right[k] = result[1]
+ elif isinstance(dict1[k], list) and isinstance(dict2[k], list):
+ result = recursive_list_diff(dict1[k], dict2[k], this_position)
+ if result:
+ left[k] = result[0]
+ right[k] = result[1]
+ elif dict1[k] != dict2[k]:
+ left[k] = dict1[k]
+ right[k] = dict2[k]
+ if left or right:
+ return left, right
+ else:
+ return None
+
+
+def get_deletions(last_applied, desired):
+ patch = {}
+ for k, last_applied_value in last_applied.items():
+ desired_value = desired.get(k)
+ if isinstance(last_applied_value, dict) and isinstance(desired_value, dict):
+ p = get_deletions(last_applied_value, desired_value)
+ if p:
+ patch[k] = p
+ elif last_applied_value != desired_value:
+ patch[k] = desired_value
+ return patch
+
+
+def get_delta(last_applied, actual, desired, position=None):
+ patch = {}
+
+ for k, desired_value in desired.items():
+ if position:
+ this_position = "%s.%s" % (position, k)
+ actual_value = actual.get(k)
+ if actual_value is None:
+ patch[k] = desired_value
+ elif isinstance(desired_value, dict):
+ p = get_delta(
+ last_applied.get(k, {}), actual_value, desired_value, this_position
+ )
+ if p:
+ patch[k] = p
+ elif isinstance(desired_value, list):
+ p = list_merge(
+ last_applied.get(k, []), actual_value, desired_value, this_position
+ )
+ if p:
+ patch[k] = [item for item in p if item is not None]
+ elif actual_value != desired_value:
+ patch[k] = desired_value
+ return patch
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/args_common.py b/ansible_collections/kubernetes/core/plugins/module_utils/args_common.py
new file mode 100644
index 00000000..27f4eacc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/args_common.py
@@ -0,0 +1,98 @@
+from __future__ import absolute_import, division, print_function
+
+from ansible.module_utils.six import string_types
+
+__metaclass__ = type
+
+
+def list_dict_str(value):
+ if isinstance(value, (list, dict, string_types)):
+ return value
+ raise TypeError
+
+
+AUTH_PROXY_HEADERS_SPEC = dict(
+ proxy_basic_auth=dict(type="str", no_log=True),
+ basic_auth=dict(type="str", no_log=True),
+ user_agent=dict(type="str"),
+)
+
+AUTH_ARG_SPEC = {
+ "kubeconfig": {"type": "raw"},
+ "context": {},
+ "host": {},
+ "api_key": {"no_log": True},
+ "username": {},
+ "password": {"no_log": True},
+ "validate_certs": {"type": "bool", "aliases": ["verify_ssl"]},
+ "ca_cert": {"type": "path", "aliases": ["ssl_ca_cert"]},
+ "client_cert": {"type": "path", "aliases": ["cert_file"]},
+ "client_key": {"type": "path", "aliases": ["key_file"]},
+ "proxy": {"type": "str"},
+ "no_proxy": {"type": "str"},
+ "proxy_headers": {"type": "dict", "options": AUTH_PROXY_HEADERS_SPEC},
+ "persist_config": {"type": "bool"},
+ "impersonate_user": {},
+ "impersonate_groups": {"type": "list", "elements": "str"},
+}
+
+WAIT_ARG_SPEC = dict(
+ wait=dict(type="bool", default=False),
+ wait_sleep=dict(type="int", default=5),
+ wait_timeout=dict(type="int", default=120),
+ wait_condition=dict(
+ type="dict",
+ default=None,
+ options=dict(
+ type=dict(),
+ status=dict(default=True, choices=[True, False, "Unknown"]),
+ reason=dict(),
+ ),
+ ),
+)
+
+# Map kubernetes-client parameters to ansible parameters
+AUTH_ARG_MAP = {
+ "kubeconfig": "kubeconfig",
+ "context": "context",
+ "host": "host",
+ "api_key": "api_key",
+ "username": "username",
+ "password": "password",
+ "verify_ssl": "validate_certs",
+ "ssl_ca_cert": "ca_cert",
+ "cert_file": "client_cert",
+ "key_file": "client_key",
+ "proxy": "proxy",
+ "no_proxy": "no_proxy",
+ "proxy_headers": "proxy_headers",
+ "persist_config": "persist_config",
+}
+
+NAME_ARG_SPEC = {
+ "kind": {},
+ "name": {},
+ "namespace": {},
+ "api_version": {"default": "v1", "aliases": ["api", "version"]},
+}
+
+COMMON_ARG_SPEC = {
+ "state": {"default": "present", "choices": ["present", "absent"]},
+ "force": {"type": "bool", "default": False},
+}
+
+RESOURCE_ARG_SPEC = {
+ "resource_definition": {"type": list_dict_str, "aliases": ["definition", "inline"]},
+ "src": {"type": "path"},
+}
+
+ARG_ATTRIBUTES_BLACKLIST = ("property_path",)
+
+DELETE_OPTS_ARG_SPEC = {
+ "propagationPolicy": {"choices": ["Foreground", "Background", "Orphan"]},
+ "gracePeriodSeconds": {"type": "int"},
+ "preconditions": {
+ "type": "dict",
+ "options": {"resourceVersion": {"type": "str"}, "uid": {"type": "str"}},
+ },
+}
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/client/discovery.py b/ansible_collections/kubernetes/core/plugins/module_utils/client/discovery.py
new file mode 100644
index 00000000..898a82ce
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/client/discovery.py
@@ -0,0 +1,216 @@
+# Copyright [2017] [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 json
+import os
+from collections import defaultdict
+import hashlib
+import tempfile
+from functools import partial
+
+import kubernetes.dynamic
+import kubernetes.dynamic.discovery
+from kubernetes import __version__
+from kubernetes.dynamic.exceptions import (
+ ResourceNotFoundError,
+ ResourceNotUniqueError,
+ ServiceUnavailableError,
+)
+
+from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
+ ResourceList,
+)
+
+
+class Discoverer(kubernetes.dynamic.discovery.Discoverer):
+ def __init__(self, client, cache_file):
+ self.client = client
+ default_cache_file_name = "k8srcp-{0}.json".format(
+ hashlib.sha256(self.__get_default_cache_id()).hexdigest()
+ )
+ self.__cache_file = cache_file or os.path.join(
+ tempfile.gettempdir(), default_cache_file_name
+ )
+ self.__init_cache()
+
+ def __get_default_cache_id(self):
+ user = self.__get_user()
+ if user:
+ cache_id = "{0}-{1}".format(self.client.configuration.host, user)
+ else:
+ cache_id = self.client.configuration.host
+ return cache_id.encode("utf-8")
+
+ def __get_user(self):
+ # This is intended to provide a portable method for getting a username.
+ # It could, and maybe should, be replaced by getpass.getuser() but, due
+ # to a lack of portability testing the original code is being left in
+ # place.
+ if hasattr(os, "getlogin"):
+ try:
+ user = os.getlogin()
+ if user:
+ return str(user)
+ except OSError:
+ pass
+ if hasattr(os, "getuid"):
+ try:
+ user = os.getuid()
+ if user:
+ return str(user)
+ except OSError:
+ pass
+ user = os.environ.get("USERNAME")
+ if user:
+ return str(user)
+ return None
+
+ def __init_cache(self, refresh=False):
+ if refresh or not os.path.exists(self.__cache_file):
+ self._cache = {"library_version": __version__}
+ refresh = True
+ else:
+ try:
+ with open(self.__cache_file, "r") as f:
+ self._cache = json.load(f, cls=partial(CacheDecoder, self.client))
+ if self._cache.get("library_version") != __version__:
+ # Version mismatch, need to refresh cache
+ self.invalidate_cache()
+ except Exception:
+ self.invalidate_cache()
+ self._load_server_info()
+ self.discover()
+ if refresh:
+ self._write_cache()
+
+ def get_resources_for_api_version(self, prefix, group, version, preferred):
+ """returns a dictionary of resources associated with provided (prefix, group, version)"""
+
+ resources = defaultdict(list)
+ subresources = defaultdict(dict)
+
+ path = "/".join(filter(None, [prefix, group, version]))
+ try:
+ resources_response = self.client.request("GET", path).resources or []
+ except ServiceUnavailableError:
+ resources_response = []
+
+ resources_raw = list(
+ filter(lambda resource: "/" not in resource["name"], resources_response)
+ )
+ subresources_raw = list(
+ filter(lambda resource: "/" in resource["name"], resources_response)
+ )
+ for subresource in subresources_raw:
+ resource, name = subresource["name"].split("/")
+ subresources[resource][name] = subresource
+
+ for resource in resources_raw:
+ # Prevent duplicate keys
+ for key in ("prefix", "group", "api_version", "client", "preferred"):
+ resource.pop(key, None)
+
+ resourceobj = kubernetes.dynamic.Resource(
+ prefix=prefix,
+ group=group,
+ api_version=version,
+ client=self.client,
+ preferred=preferred,
+ subresources=subresources.get(resource["name"]),
+ **resource
+ )
+ resources[resource["kind"]].append(resourceobj)
+
+ resource_lookup = {
+ "prefix": prefix,
+ "group": group,
+ "api_version": version,
+ "kind": resourceobj.kind,
+ "name": resourceobj.name,
+ }
+ resource_list = ResourceList(
+ self.client,
+ group=group,
+ api_version=version,
+ base_kind=resource["kind"],
+ base_resource_lookup=resource_lookup,
+ )
+ resources[resource_list.kind].append(resource_list)
+ return resources
+
+ def get(self, **kwargs):
+ """
+ Same as search, but will throw an error if there are multiple or no
+ results. If there are multiple results and only one is an exact match
+ on api_version, that resource will be returned.
+ """
+ results = self.search(**kwargs)
+ # If there are multiple matches, prefer exact matches on api_version
+ if len(results) > 1 and kwargs.get("api_version"):
+ results = [
+ result
+ for result in results
+ if result.group_version == kwargs["api_version"]
+ ]
+ # If there are multiple matches, prefer non-List kinds
+ if len(results) > 1 and not all(isinstance(x, ResourceList) for x in results):
+ results = [
+ result for result in results if not isinstance(result, ResourceList)
+ ]
+ # if multiple resources are found that share a GVK, prefer the one with the most supported verbs
+ if (
+ len(results) > 1
+ and len(set((x.group_version, x.kind) for x in results)) == 1
+ ):
+ if len(set(len(x.verbs) for x in results)) != 1:
+ results = [max(results, key=lambda x: len(x.verbs))]
+ if len(results) == 1:
+ return results[0]
+ elif not results:
+ raise ResourceNotFoundError("No matches found for {0}".format(kwargs))
+ else:
+ raise ResourceNotUniqueError(
+ "Multiple matches found for {0}: {1}".format(kwargs, results)
+ )
+
+
+class LazyDiscoverer(Discoverer, kubernetes.dynamic.LazyDiscoverer):
+ def __init__(self, client, cache_file):
+ 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):
+ self.client = client
+ json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
+
+ def object_hook(self, obj):
+ if "_type" not in obj:
+ return obj
+ _type = obj.pop("_type")
+ if _type == "Resource":
+ return kubernetes.dynamic.Resource(client=self.client, **obj)
+ elif _type == "ResourceList":
+ return ResourceList(self.client, **obj)
+ elif _type == "ResourceGroup":
+ return kubernetes.dynamic.discovery.ResourceGroup(
+ obj["preferred"], resources=self.object_hook(obj["resources"])
+ )
+ return obj
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/client/resource.py b/ansible_collections/kubernetes/core/plugins/module_utils/client/resource.py
new file mode 100644
index 00000000..3c0d402a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/client/resource.py
@@ -0,0 +1,60 @@
+# Copyright [2017] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+import kubernetes.dynamic
+
+
+class ResourceList(kubernetes.dynamic.resource.ResourceList):
+ def __init__(
+ self,
+ client,
+ group="",
+ api_version="v1",
+ base_kind="",
+ kind=None,
+ base_resource_lookup=None,
+ ):
+ self.client = client
+ self.group = group
+ self.api_version = api_version
+ self.kind = kind or "{0}List".format(base_kind)
+ self.base_kind = base_kind
+ self.base_resource_lookup = base_resource_lookup
+ self.__base_resource = None
+
+ def base_resource(self):
+ if self.__base_resource:
+ return self.__base_resource
+ elif self.base_resource_lookup:
+ self.__base_resource = self.client.resources.get(
+ **self.base_resource_lookup
+ )
+ return self.__base_resource
+ return None
+
+ def to_dict(self):
+ return {
+ "_type": "ResourceList",
+ "group": self.group,
+ "api_version": self.api_version,
+ "kind": self.kind,
+ "base_kind": self.base_kind,
+ "base_resource_lookup": self.base_resource_lookup,
+ }
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/common.py b/ansible_collections/kubernetes/core/plugins/module_utils/common.py
new file mode 100644
index 00000000..a46c813f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/common.py
@@ -0,0 +1,1505 @@
+# Copyright 2018 Red Hat | Ansible
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import base64
+import time
+import os
+import traceback
+import sys
+import hashlib
+from datetime import datetime
+from tempfile import NamedTemporaryFile
+
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_MAP,
+ AUTH_ARG_SPEC,
+ AUTH_PROXY_HEADERS_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
+ generate_hash,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
+ LabelSelectorFilter,
+)
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible.module_utils.six import iteritems, string_types
+from ansible.module_utils._text import to_native, to_bytes, to_text
+from ansible.module_utils.common.dict_transformations import dict_merge
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.urls import Request
+
+K8S_IMP_ERR = None
+try:
+ import kubernetes
+ from kubernetes.dynamic.exceptions import (
+ NotFoundError,
+ ResourceNotFoundError,
+ ResourceNotUniqueError,
+ DynamicApiError,
+ ConflictError,
+ ForbiddenError,
+ MethodNotAllowedError,
+ BadRequestError,
+ KubernetesValidateMissing,
+ )
+
+ HAS_K8S_MODULE_HELPER = True
+ k8s_import_exception = None
+except ImportError as e:
+ HAS_K8S_MODULE_HELPER = False
+ k8s_import_exception = e
+ K8S_IMP_ERR = traceback.format_exc()
+
+IMP_K8S_CLIENT = None
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils import (
+ k8sdynamicclient,
+ )
+ from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import (
+ LazyDiscoverer,
+ )
+
+ IMP_K8S_CLIENT = True
+except ImportError as e:
+ IMP_K8S_CLIENT = False
+ k8s_client_import_exception = e
+ IMP_K8S_CLIENT_ERR = traceback.format_exc()
+
+YAML_IMP_ERR = None
+try:
+ import yaml
+
+ HAS_YAML = True
+except ImportError:
+ YAML_IMP_ERR = traceback.format_exc()
+ HAS_YAML = False
+
+HAS_K8S_APPLY = None
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
+ apply_object,
+ )
+
+ HAS_K8S_APPLY = True
+except ImportError:
+ HAS_K8S_APPLY = False
+
+try:
+ import urllib3
+
+ urllib3.disable_warnings()
+except ImportError:
+ pass
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
+ recursive_diff,
+ )
+except ImportError:
+ from ansible.module_utils.common.dict_transformations import recursive_diff
+
+try:
+ from kubernetes.dynamic.resource import ResourceInstance
+
+ HAS_K8S_INSTANCE_HELPER = True
+ k8s_import_exception = None
+except ImportError as e:
+ HAS_K8S_INSTANCE_HELPER = False
+ k8s_import_exception = e
+ K8S_IMP_ERR = traceback.format_exc()
+
+
+def configuration_digest(configuration, **kwargs):
+ """This function has been deprecated and will be removed in version 4.0.0."""
+ m = hashlib.sha256()
+ for k in AUTH_ARG_MAP:
+ if not hasattr(configuration, k):
+ v = None
+ else:
+ v = getattr(configuration, k)
+ if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
+ with open(str(v), "r") as fd:
+ content = fd.read()
+ m.update(content.encode())
+ else:
+ m.update(str(v).encode())
+ for k in kwargs:
+ content = "{0}: {1}".format(k, kwargs.get(k))
+ m.update(content.encode())
+ digest = m.hexdigest()
+ return digest
+
+
+class unique_string(str):
+ """This function has been deprecated and will be removed in version 4.0.0."""
+
+ _low = None
+
+ def __hash__(self):
+ return id(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def lower(self):
+ if self._low is None:
+ lower = str.lower(self)
+ if str.__eq__(lower, self):
+ self._low = self
+ else:
+ self._low = unique_string(lower)
+ return self._low
+
+
+def get_api_client(module=None, **kwargs):
+ """This function has been deprecated and will be removed in version 4.0.0. Please use module_utils.k8s.client.get_api_client() instead."""
+ auth = {}
+
+ def _raise_or_fail(exc, msg):
+ if module:
+ 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:
+ auth[true_name] = module.params.get(arg_name)
+ elif arg_name in kwargs and kwargs.get(arg_name) is not None:
+ auth[true_name] = kwargs.get(arg_name)
+ elif arg_name == "proxy_headers":
+ # specific case for 'proxy_headers' which is a dictionary
+ proxy_headers = {}
+ for key in AUTH_PROXY_HEADERS_SPEC.keys():
+ env_value = os.getenv(
+ "K8S_AUTH_PROXY_HEADERS_{0}".format(key.upper()), None
+ )
+ if env_value is not None:
+ if AUTH_PROXY_HEADERS_SPEC[key].get("type") == "bool":
+ env_value = env_value.lower() not in ["0", "false", "no"]
+ proxy_headers[key] = env_value
+ if proxy_headers is not {}:
+ auth[true_name] = proxy_headers
+ else:
+ env_value = os.getenv(
+ "K8S_AUTH_{0}".format(arg_name.upper()), None
+ ) or os.getenv("K8S_AUTH_{0}".format(true_name.upper()), None)
+ if env_value is not None:
+ if AUTH_ARG_SPEC[arg_name].get("type") == "bool":
+ env_value = env_value.lower() not in ["0", "false", "no"]
+ auth[true_name] = env_value
+
+ def auth_set(*names):
+ return all(auth.get(name) for name in names)
+
+ 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
+ auth["host"] = auth.get("host").rstrip("/")
+
+ if auth_set("no_proxy"):
+ if LooseVersion(kubernetes.__version__) < LooseVersion("19.15.0"):
+ _raise_or_fail(
+ Exception("kubernetes >= 19.15.0 is required to use no_proxy feature."),
+ "Failed to set no_proxy due to: %s",
+ )
+
+ configuration = None
+ if auth_set("username", "password", "host") or auth_set("api_key", "host"):
+ # We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
+ arg_init = {}
+ # api_key will be set later in this function
+ for key in ("username", "password", "host"):
+ arg_init[key] = auth.get(key)
+ configuration = kubernetes.client.Configuration(**arg_init)
+ elif auth_set("kubeconfig") or auth_set("context"):
+ try:
+ _load_config()
+ except Exception as err:
+ _raise_or_fail(err, "Failed to load kubeconfig due to %s")
+
+ else:
+ # First try to do incluster config, then kubeconfig
+ try:
+ kubernetes.config.load_incluster_config()
+ except kubernetes.config.ConfigException:
+ try:
+ _load_config()
+ except Exception as err:
+ _raise_or_fail(err, "Failed to load kubeconfig due to %s")
+
+ # Override any values in the default configuration with Ansible parameters
+ # As of kubernetes-client v12.0.0, get_default_copy() is required here
+ if not configuration:
+ try:
+ configuration = kubernetes.client.Configuration().get_default_copy()
+ except AttributeError:
+ configuration = kubernetes.client.Configuration()
+
+ for key, value in iteritems(auth):
+ if key in AUTH_ARG_MAP.keys() and value is not None:
+ if key == "api_key":
+ setattr(
+ configuration, key, {"authorization": "Bearer {0}".format(value)}
+ )
+ elif key == "proxy_headers":
+ headers = urllib3.util.make_headers(**value)
+ setattr(configuration, key, headers)
+ else:
+ setattr(configuration, key, value)
+
+ api_client = kubernetes.client.ApiClient(configuration)
+ impersonate_map = {
+ "impersonate_user": "Impersonate-User",
+ "impersonate_groups": "Impersonate-Group",
+ }
+ api_digest = {}
+
+ headers = {}
+ for arg_name, header_name in impersonate_map.items():
+ value = None
+ if module and module.params.get(arg_name) is not None:
+ value = module.params.get(arg_name)
+ elif arg_name in kwargs and kwargs.get(arg_name) is not None:
+ value = kwargs.get(arg_name)
+ else:
+ value = os.getenv("K8S_AUTH_{0}".format(arg_name.upper()), None)
+ if value is not None:
+ if AUTH_ARG_SPEC[arg_name].get("type") == "list":
+ value = [x for x in env_value.split(",") if x != ""]
+ if value:
+ if isinstance(value, list):
+ api_digest[header_name] = ",".join(sorted(value))
+ for v in value:
+ api_client.set_default_header(
+ header_name=unique_string(header_name), header_value=v
+ )
+ else:
+ api_digest[header_name] = value
+ api_client.set_default_header(
+ header_name=header_name, header_value=value
+ )
+
+ digest = configuration_digest(configuration, **api_digest)
+ if digest in get_api_client._pool:
+ client = get_api_client._pool[digest]
+ return client
+
+ try:
+ client = k8sdynamicclient.K8SDynamicClient(
+ api_client, discoverer=LazyDiscoverer
+ )
+ except Exception as err:
+ _raise_or_fail(err, "Failed to get client due to %s")
+
+ get_api_client._pool[digest] = client
+ return client
+
+
+get_api_client._pool = {}
+
+
+def fetch_file_from_url(module, url):
+ # Download file
+ bufsize = 65536
+ file_name, file_ext = os.path.splitext(str(url.rsplit("/", 1)[1]))
+ temp_file = NamedTemporaryFile(
+ dir=module.tmpdir, prefix=file_name, suffix=file_ext, delete=False
+ )
+ module.add_cleanup_file(temp_file.name)
+ try:
+ rsp = Request().open("GET", url)
+ if not rsp:
+ module.fail_json(msg="Failure downloading %s" % url)
+ data = rsp.read(bufsize)
+ while data:
+ temp_file.write(data)
+ data = rsp.read(bufsize)
+ temp_file.close()
+ except Exception as e:
+ module.fail_json(msg="Failure downloading %s, %s" % (url, to_native(e)))
+ return temp_file.name
+
+
+class K8sAnsibleMixin(object):
+ def __init__(self, module, pyyaml_required=True, *args, **kwargs):
+ module.deprecate(
+ msg="The K8sAnsibleMixin class has been deprecated and refactored into the module_utils/k8s/ directory.",
+ version="4.0.0",
+ collection_name="kubernetes.core",
+ )
+ 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__
+ self.supports_dry_run = LooseVersion(self.kubernetes_version) >= LooseVersion(
+ "18.20.0"
+ )
+
+ if pyyaml_required and not HAS_YAML:
+ module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
+
+ def _find_resource_with_prefix(self, prefix, kind, api_version):
+ for attribute in ["kind", "name", "singular_name"]:
+ try:
+ return self.client.resources.get(
+ **{"prefix": prefix, "api_version": api_version, attribute: kind}
+ )
+ except (ResourceNotFoundError, ResourceNotUniqueError):
+ pass
+ return self.client.resources.get(
+ prefix=prefix, api_version=api_version, short_names=[kind]
+ )
+
+ def find_resource(self, kind, api_version, fail=False):
+ msg = "Failed to find exact match for {0}.{1} by [kind, name, singularName, shortNames]".format(
+ api_version, kind
+ )
+ try:
+ if api_version == "v1":
+ return self._find_resource_with_prefix("api", kind, api_version)
+ except ResourceNotUniqueError:
+ # No point trying again as we'll just get the same error
+ if fail:
+ self.fail(msg=msg)
+ else:
+ return None
+ except ResourceNotFoundError:
+ pass
+ # The second check without a prefix is maintained for backwards compatibility.
+ # If we are here, it's probably because the resource really doesn't exist or
+ # the wrong api_version has been specified, for example, v1.DaemonSet.
+ try:
+ return self._find_resource_with_prefix(None, kind, api_version)
+ except (ResourceNotFoundError, ResourceNotUniqueError):
+ if fail:
+ self.fail(msg=msg)
+
+ def kubernetes_facts(
+ self,
+ kind,
+ api_version,
+ name=None,
+ namespace=None,
+ label_selectors=None,
+ field_selectors=None,
+ wait=False,
+ wait_sleep=5,
+ wait_timeout=120,
+ state="present",
+ condition=None,
+ ):
+ resource = self.find_resource(kind, api_version)
+ api_found = bool(resource)
+ if not api_found:
+ return dict(
+ resources=[],
+ msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(
+ api_version, kind
+ ),
+ api_found=False,
+ )
+
+ if not label_selectors:
+ label_selectors = []
+ if not field_selectors:
+ field_selectors = []
+
+ result = None
+ params = dict(
+ name=name,
+ namespace=namespace,
+ label_selector=",".join(label_selectors),
+ field_selector=",".join(field_selectors),
+ )
+ try:
+ result = resource.get(**params)
+ except BadRequestError:
+ return dict(resources=[], api_found=True)
+ except NotFoundError:
+ if not wait or name is None:
+ return dict(resources=[], api_found=True)
+ except Exception as e:
+ if not wait or name is None:
+ err = "Exception '{0}' raised while trying to get resource using {1}".format(
+ e, params
+ )
+ return dict(resources=[], msg=err, api_found=True)
+
+ if not wait:
+ result = result.to_dict()
+ if "items" in result:
+ return dict(resources=result["items"], api_found=True)
+ return dict(resources=[result], api_found=True)
+
+ start = datetime.now()
+
+ def _elapsed():
+ return (datetime.now() - start).seconds
+
+ def result_empty(result):
+ return (
+ result is None
+ or result.kind.endswith("List")
+ and not result.get("items")
+ )
+
+ last_exception = None
+ while result_empty(result) and _elapsed() < wait_timeout:
+ try:
+ result = resource.get(**params)
+ except NotFoundError:
+ pass
+ except Exception as e:
+ last_exception = e
+ if not result_empty(result):
+ break
+ time.sleep(wait_sleep)
+ if result_empty(result):
+ res = dict(resources=[], api_found=True)
+ if last_exception is not None:
+ res[
+ "msg"
+ ] = "Exception '%s' raised while trying to get resource using %s" % (
+ last_exception,
+ params,
+ )
+ return res
+
+ if isinstance(result, ResourceInstance):
+ satisfied_by = []
+ # We have a list of ResourceInstance
+ resource_list = result.get("items", [])
+ if not resource_list:
+ resource_list = [result]
+
+ for resource_instance in resource_list:
+ success, res, duration = self.wait(
+ resource,
+ resource_instance,
+ sleep=wait_sleep,
+ timeout=wait_timeout,
+ state=state,
+ condition=condition,
+ )
+ if not success:
+ self.fail(
+ msg="Failed to gather information about %s(s) even"
+ " after waiting for %s seconds" % (res.get("kind"), duration)
+ )
+ satisfied_by.append(res)
+ return dict(resources=satisfied_by, api_found=True)
+ result = result.to_dict()
+
+ if "items" in result:
+ return dict(resources=result["items"], api_found=True)
+ return dict(resources=[result], api_found=True)
+
+ def remove_aliases(self):
+ """
+ The helper doesn't know what to do with aliased keys
+ """
+ for k, v in iteritems(self.argspec):
+ if "aliases" in v:
+ for alias in v["aliases"]:
+ if alias in self.params:
+ self.params.pop(alias)
+
+ def load_resource_definitions(self, src, module=None):
+ """Load the requested src path"""
+ if module and (
+ src.startswith("https://")
+ or src.startswith("http://")
+ or src.startswith("ftp://")
+ ):
+ src = fetch_file_from_url(module, src)
+
+ result = None
+ path = os.path.normpath(src)
+ if not os.path.exists(path):
+ self.fail(msg="Error accessing {0}. Does the file exist?".format(path))
+ try:
+ with open(path, "rb") as f:
+ result = list(yaml.safe_load_all(f))
+ except (IOError, yaml.YAMLError) as exc:
+ self.fail(msg="Error loading resource_definition: {0}".format(exc))
+ return result
+
+ def diff_objects(self, existing, new):
+ result = dict()
+ diff = recursive_diff(existing, new)
+ if not diff:
+ return True, result
+
+ result["before"] = diff[0]
+ result["after"] = diff[1]
+
+ # If only metadata.generation and metadata.resourceVersion changed, ignore it
+ ignored_keys = set(["generation", "resourceVersion"])
+
+ if list(result["after"].keys()) != ["metadata"] or list(
+ result["before"].keys()
+ ) != ["metadata"]:
+ return False, result
+
+ if not set(result["after"]["metadata"].keys()).issubset(ignored_keys):
+ return False, result
+ if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
+ return False, result
+
+ if hasattr(self, "warn"):
+ self.warn(
+ "No meaningful diff was generated, but the API may not be idempotent (only metadata.generation or metadata.resourceVersion were changed)"
+ )
+
+ return True, result
+
+ def fail(self, msg=None):
+ self.fail_json(msg=msg)
+
+ def _wait_for(
+ self,
+ resource,
+ name,
+ namespace,
+ predicate,
+ sleep,
+ timeout,
+ state,
+ label_selectors=None,
+ ):
+ start = datetime.now()
+
+ def _wait_for_elapsed():
+ return (datetime.now() - start).seconds
+
+ response = None
+ while _wait_for_elapsed() < timeout:
+ try:
+ 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()
+ return True, {}, _wait_for_elapsed()
+ time.sleep(sleep)
+ except NotFoundError:
+ if state == "absent":
+ return True, {}, _wait_for_elapsed()
+ except Exception:
+ pass
+ if response:
+ response = response.to_dict()
+ return False, response, _wait_for_elapsed()
+
+ 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
+ # Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
+ # deployment.status.replicas is None is perfectly ok if desired replicas == 0
+ # Scaling up means that we also need to check that we're not in a
+ # situation where status.replicas == status.availableReplicas
+ # but spec.replicas != status.replicas
+ return (
+ deployment.status
+ and deployment.spec.replicas == (deployment.status.replicas or 0)
+ and deployment.status.availableReplicas == deployment.status.replicas
+ and deployment.status.observedGeneration
+ == deployment.metadata.generation
+ and not deployment.status.unavailableReplicas
+ )
+
+ def _pod_ready(pod):
+ return (
+ pod.status
+ and pod.status.containerStatuses is not None
+ and all(container.ready for container in pod.status.containerStatuses)
+ )
+
+ def _daemonset_ready(daemonset):
+ return (
+ daemonset.status
+ and daemonset.status.desiredNumberScheduled is not None
+ and daemonset.status.updatedNumberScheduled
+ == daemonset.status.desiredNumberScheduled
+ and daemonset.status.numberReady
+ == daemonset.status.desiredNumberScheduled
+ and daemonset.status.observedGeneration == daemonset.metadata.generation
+ and not daemonset.status.unavailableReplicas
+ )
+
+ def _statefulset_ready(statefulset):
+ # These may be None
+ updated_replicas = statefulset.status.updatedReplicas or 0
+ ready_replicas = statefulset.status.readyReplicas or 0
+
+ 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 updated_replicas == statefulset.spec.replicas
+ and ready_replicas == 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
+ match = [
+ x for x in resource.status.conditions if x.type == condition["type"]
+ ]
+ if not match:
+ return False
+ # There should never be more than one condition of a specific type
+ match = match[0]
+ if match.status == "Unknown":
+ if match.status == condition["status"]:
+ if "reason" not in condition:
+ return True
+ if condition["reason"]:
+ return match.reason == condition["reason"]
+ return False
+ status = True if match.status == "True" else False
+ if status == boolean(condition["status"], strict=False):
+ if condition.get("reason"):
+ return match.reason == condition["reason"]
+ return True
+ return False
+
+ def _resource_absent(resource):
+ return not resource 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":
+ predicate = (
+ waiter.get(kind, lambda x: x) if not condition else _custom_condition
+ )
+ else:
+ predicate = _resource_absent
+ 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")
+
+ self.resource_definitions = []
+ if resource_definition:
+ if isinstance(resource_definition, string_types):
+ try:
+ self.resource_definitions = yaml.safe_load_all(resource_definition)
+ except (IOError, yaml.YAMLError) as exc:
+ self.fail(msg="Error loading resource_definition: {0}".format(exc))
+ elif isinstance(resource_definition, list):
+ for resource in resource_definition:
+ if isinstance(resource, string_types):
+ yaml_data = yaml.safe_load_all(resource)
+ for item in yaml_data:
+ if item is not None:
+ self.resource_definitions.append(item)
+ else:
+ self.resource_definitions.append(resource)
+ else:
+ self.resource_definitions = [resource_definition]
+
+ src = module.params.get("src")
+ if src:
+ self.resource_definitions = self.load_resource_definitions(src, module)
+ try:
+ self.resource_definitions = [
+ item for item in self.resource_definitions if item
+ ]
+ except AttributeError:
+ pass
+
+ if not resource_definition and not src:
+ implicit_definition = dict(
+ kind=module.params["kind"],
+ apiVersion=module.params["api_version"],
+ metadata=dict(name=module.params["name"]),
+ )
+ if module.params.get("namespace"):
+ implicit_definition["metadata"]["namespace"] = module.params.get(
+ "namespace"
+ )
+ self.resource_definitions = [implicit_definition]
+
+ def check_library_version(self):
+ if LooseVersion(self.kubernetes_version) < LooseVersion("12.0.0"):
+ self.fail_json(msg="kubernetes >= 12.0.0 is required")
+
+ def flatten_list_kind(self, list_resource, definitions):
+ flattened = []
+ parent_api_version = list_resource.group_version if list_resource else None
+ parent_kind = list_resource.kind[:-4] if list_resource else None
+ for definition in definitions.get("items", []):
+ resource = self.find_resource(
+ definition.get("kind", parent_kind),
+ definition.get("apiVersion", parent_api_version),
+ fail=True,
+ )
+ flattened.append((resource, self.set_defaults(resource, definition)))
+ return flattened
+
+ def execute_module(self):
+ changed = False
+ results = []
+ try:
+ self.client = get_api_client(self.module)
+ # Hopefully the kubernetes client will provide its own exception class one day
+ except (urllib3.exceptions.RequestError) as e:
+ self.fail_json(msg="Couldn't connect to Kubernetes: %s" % str(e))
+
+ flattened_definitions = []
+ for definition in self.resource_definitions:
+ if definition is None:
+ continue
+ kind = definition.get("kind", self.kind)
+ api_version = definition.get("apiVersion", self.api_version)
+ if kind and kind.endswith("List"):
+ resource = self.find_resource(kind, api_version, fail=False)
+ flattened_definitions.extend(
+ self.flatten_list_kind(resource, definition)
+ )
+ else:
+ resource = self.find_resource(kind, api_version, fail=True)
+ flattened_definitions.append((resource, definition))
+
+ for (resource, definition) in flattened_definitions:
+ kind = definition.get("kind", self.kind)
+ api_version = definition.get("apiVersion", self.api_version)
+ definition = self.set_defaults(resource, definition)
+ self.warnings = []
+ if self.params["validate"] is not None:
+ self.warnings = self.validate(definition)
+ result = self.perform_action(resource, definition)
+ if self.warnings:
+ result["warnings"] = self.warnings
+ changed = changed or result["changed"]
+ results.append(result)
+
+ if len(results) == 1:
+ self.exit_json(**results[0])
+
+ self.exit_json(**{"changed": changed, "result": {"results": results}})
+
+ def validate(self, resource):
+ def _prepend_resource_info(resource, msg):
+ return "%s %s: %s" % (resource["kind"], resource["metadata"]["name"], msg)
+
+ try:
+ warnings, errors = self.client.validate(
+ resource,
+ self.params["validate"].get("version"),
+ self.params["validate"].get("strict"),
+ )
+ except KubernetesValidateMissing:
+ self.fail_json(
+ msg="kubernetes-validate python library is required to validate resources"
+ )
+
+ if errors and self.params["validate"]["fail_on_error"]:
+ self.fail_json(
+ msg="\n".join(
+ [_prepend_resource_info(resource, error) for error in errors]
+ )
+ )
+ else:
+ return [_prepend_resource_info(resource, msg) for msg in warnings + errors]
+
+ def set_defaults(self, resource, definition):
+ definition["kind"] = resource.kind
+ definition["apiVersion"] = resource.group_version
+ metadata = definition.get("metadata", {})
+ if not metadata.get("name") and not metadata.get("generateName"):
+ if hasattr(self, "name") and self.name:
+ metadata["name"] = self.name
+ elif hasattr(self, "generate_name") and self.generate_name:
+ metadata["generateName"] = self.generate_name
+ if resource.namespaced and self.namespace and not metadata.get("namespace"):
+ metadata["namespace"] = self.namespace
+ definition["metadata"] = metadata
+ return definition
+
+ def perform_action(self, resource, definition):
+ append_hash = self.params.get("append_hash", False)
+ apply = self.params.get("apply", False)
+ delete_options = self.params.get("delete_options")
+ result = {"changed": False, "result": {}}
+ state = self.params.get("state", None)
+ force = self.params.get("force", False)
+ name = definition["metadata"].get("name")
+ generate_name = definition["metadata"].get("generateName")
+ origin_name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+ existing = None
+ wait = self.params.get("wait")
+ wait_sleep = self.params.get("wait_sleep")
+ wait_timeout = self.params.get("wait_timeout")
+ wait_condition = None
+ continue_on_error = self.params.get("continue_on_error")
+ label_selectors = self.params.get("label_selectors")
+ server_side_apply = self.params.get("server_side_apply")
+ if self.params.get("wait_condition") and self.params["wait_condition"].get(
+ "type"
+ ):
+ wait_condition = self.params["wait_condition"]
+
+ def build_error_msg(kind, name, msg):
+ return "%s %s: %s" % (kind, name, msg)
+
+ self.remove_aliases()
+
+ try:
+ # ignore append_hash for resources other than ConfigMap and Secret
+ if append_hash and definition["kind"] in ["ConfigMap", "Secret"]:
+ if name:
+ name = "%s-%s" % (name, generate_hash(definition))
+ definition["metadata"]["name"] = name
+ elif generate_name:
+ definition["metadata"]["generateName"] = "%s-%s" % (
+ generate_name,
+ generate_hash(definition),
+ )
+ params = {}
+ if name:
+ params["name"] = name
+ if namespace:
+ params["namespace"] = namespace
+ if label_selectors:
+ params["label_selector"] = ",".join(label_selectors)
+
+ if "name" in params or "label_selector" in params:
+ existing = resource.get(**params)
+ elif state == "absent":
+ msg = (
+ "At least one of name|label_selectors is required to delete object."
+ )
+ if continue_on_error:
+ result["error"] = dict(msg=msg)
+ return result
+ else:
+ self.fail_json(msg=msg)
+ except (NotFoundError, MethodNotAllowedError):
+ # Remove traceback so that it doesn't show up in later failures
+ try:
+ sys.exc_clear()
+ except AttributeError:
+ # no sys.exc_clear on python3
+ pass
+ except ForbiddenError as exc:
+ if (
+ definition["kind"] in ["Project", "ProjectRequest"]
+ and state != "absent"
+ ):
+ return self.create_project_request(definition)
+ msg = "Failed to retrieve requested object: {0}".format(exc.body)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ except DynamicApiError as exc:
+ msg = "Failed to retrieve requested object: {0}".format(exc.body)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ except ValueError as value_exc:
+ msg = "Failed to retrieve requested object: {0}".format(
+ to_native(value_exc)
+ )
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error="",
+ status="",
+ reason="",
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error="",
+ status="",
+ reason="",
+ )
+
+ if state == "absent":
+ result["method"] = "delete"
+
+ 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:
+ # Delete the object
+ result["changed"] = True
+ if self.check_mode and not self.supports_dry_run:
+ return result
+ else:
+ params = {"namespace": namespace}
+ if delete_options:
+ body = {
+ "apiVersion": "v1",
+ "kind": "DeleteOptions",
+ }
+ body.update(delete_options)
+ params["body"] = body
+ if self.check_mode:
+ params["dry_run"] = "All"
+ try:
+ if existing.kind.endswith("List"):
+ result["result"] = []
+ for item in existing.items:
+ origin_name = item.metadata.name
+ params["name"] = origin_name
+ k8s_obj = resource.delete(**params)
+ result["result"].append(k8s_obj.to_dict())
+ else:
+ origin_name = existing.metadata.name
+ params["name"] = origin_name
+ k8s_obj = resource.delete(**params)
+ result["result"] = k8s_obj.to_dict()
+ except DynamicApiError as exc:
+ msg = "Failed to delete object: {0}".format(exc.body)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ self.fail_json(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ if wait and not self.check_mode:
+ 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"
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ **result
+ )
+ return result
+ self.fail_json(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ **result
+ )
+ 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 and not self.supports_dry_run:
+ ignored, patch = apply_object(
+ resource, _encode_stringdata(definition)
+ )
+ if existing:
+ k8s_obj = dict_merge(existing.to_dict(), patch)
+ else:
+ k8s_obj = patch
+ else:
+ try:
+ params = {}
+ if self.check_mode:
+ params["dry_run"] = "All"
+ if server_side_apply:
+ params["server_side"] = True
+ if LooseVersion(kubernetes.__version__) < LooseVersion(
+ "19.15.0"
+ ):
+ msg = "kubernetes >= 19.15.0 is required to use server side apply."
+ if continue_on_error:
+ result["error"] = dict(msg=msg)
+ return result
+ else:
+ self.fail_json(
+ msg=msg, version=kubernetes.__version__
+ )
+ if not server_side_apply.get("field_manager"):
+ self.fail(
+ msg="field_manager is required to use server side apply."
+ )
+ params.update(server_side_apply)
+ k8s_obj = resource.apply(
+ definition, namespace=namespace, **params
+ ).to_dict()
+ except DynamicApiError as exc:
+ msg = "Failed to apply object: {0}".format(exc.body)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ success = True
+ result["result"] = k8s_obj
+ if wait and not self.check_mode:
+ success, result["result"], result["duration"] = self.wait(
+ resource,
+ definition,
+ wait_sleep,
+ wait_timeout,
+ condition=wait_condition,
+ )
+ if existing:
+ existing = existing.to_dict()
+ else:
+ existing = {}
+ match, diffs = self.diff_objects(existing, result["result"])
+ result["changed"] = not match
+ if self.module._diff:
+ result["diff"] = diffs
+ result["method"] = "apply"
+ if not success:
+ msg = "Resource apply timed out"
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ **result
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ **result
+ )
+ return result
+
+ if not existing:
+ if state == "patched":
+ # Silently skip this resource (do not raise an error) as 'patch_only' is set to true
+ result["changed"] = False
+ result[
+ "warning"
+ ] = "resource 'kind={kind},name={name}' was not found but will not be created as 'state'\
+ parameter has been set to '{state}'".format(
+ kind=definition["kind"], name=origin_name, state=state
+ )
+ return result
+ elif self.check_mode and not self.supports_dry_run:
+ k8s_obj = _encode_stringdata(definition)
+ else:
+ params = {}
+ if self.check_mode:
+ params["dry_run"] = "All"
+ try:
+ k8s_obj = resource.create(
+ definition, namespace=namespace, **params
+ ).to_dict()
+ except ConflictError:
+ # Some resources, like ProjectRequests, can't be created multiple times,
+ # because the resources that they create don't match their kind
+ # In this case we'll mark it as unchanged and warn the user
+ self.warn(
+ "{0} was not found, but creating it returned a 409 Conflict error. This can happen \
+ if the resource you are creating does not directly create a resource of the same kind.".format(
+ name
+ )
+ )
+ return result
+ except DynamicApiError as exc:
+ msg = "Failed to create object: {0}".format(exc.body)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ except Exception as exc:
+ msg = "Failed to create object: {0}".format(exc)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error="",
+ status="",
+ reason="",
+ )
+ return result
+ else:
+ self.fail_json(msg=msg, error="", status="", reason="")
+ success = True
+ result["result"] = k8s_obj
+ if wait and not self.check_mode:
+ definition["metadata"].update({"name": k8s_obj["metadata"]["name"]})
+ success, result["result"], result["duration"] = self.wait(
+ resource,
+ definition,
+ wait_sleep,
+ wait_timeout,
+ condition=wait_condition,
+ )
+ result["changed"] = True
+ result["method"] = "create"
+ if not success:
+ msg = "Resource creation timed out"
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ **result
+ )
+ return result
+ else:
+ self.fail_json(msg=msg, **result)
+ return result
+
+ match = False
+ diffs = []
+
+ if state == "present" and existing and force:
+ if self.check_mode and not self.supports_dry_run:
+ k8s_obj = _encode_stringdata(definition)
+ else:
+ params = {}
+ if self.check_mode:
+ params["dry_run"] = "All"
+ try:
+ k8s_obj = resource.replace(
+ definition,
+ name=name,
+ namespace=namespace,
+ append_hash=append_hash,
+ **params
+ ).to_dict()
+ except DynamicApiError as exc:
+ msg = "Failed to replace object: {0}".format(exc.body)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(
+ definition["kind"], origin_name, msg
+ ),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ return result
+ else:
+ self.fail_json(
+ msg=msg,
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
+ success = True
+ result["result"] = k8s_obj
+ if wait and not self.check_mode:
+ success, result["result"], result["duration"] = self.wait(
+ resource,
+ definition,
+ wait_sleep,
+ wait_timeout,
+ condition=wait_condition,
+ )
+ match, diffs = self.diff_objects(existing.to_dict(), result["result"])
+ result["changed"] = not match
+ result["method"] = "replace"
+ if self.module._diff:
+ result["diff"] = diffs
+ if not success:
+ msg = "Resource replacement timed out"
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ **result
+ )
+ return result
+ else:
+ self.fail_json(msg=msg, **result)
+ return result
+
+ # Differences exist between the existing obj and requested params
+ if self.check_mode and not self.supports_dry_run:
+ k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition))
+ else:
+ for merge_type in self.params["merge_type"] or [
+ "strategic-merge",
+ "merge",
+ ]:
+ k8s_obj, error = self.patch_resource(
+ resource,
+ definition,
+ existing,
+ name,
+ namespace,
+ merge_type=merge_type,
+ )
+ if not error:
+ break
+ if error:
+ if continue_on_error:
+ result["error"] = error
+ result["error"]["msg"] = build_error_msg(
+ definition["kind"], origin_name, result["error"].get("msg")
+ )
+ return result
+ else:
+ self.fail_json(**error)
+
+ success = True
+ result["result"] = k8s_obj
+ if wait and not self.check_mode:
+ success, result["result"], result["duration"] = self.wait(
+ resource,
+ definition,
+ wait_sleep,
+ wait_timeout,
+ condition=wait_condition,
+ )
+ match, diffs = self.diff_objects(existing.to_dict(), result["result"])
+ result["changed"] = not match
+ result["method"] = "patch"
+ if self.module._diff:
+ result["diff"] = diffs
+
+ if not success:
+ msg = "Resource update timed out"
+ if continue_on_error:
+ result["error"] = dict(
+ msg=build_error_msg(definition["kind"], origin_name, msg),
+ **result
+ )
+ return result
+ else:
+ self.fail_json(msg=msg, **result)
+ return result
+
+ def patch_resource(
+ self, resource, definition, existing, name, namespace, merge_type=None
+ ):
+ if merge_type == "json":
+ self.module.deprecate(
+ msg="json as a merge_type value is deprecated. Please use the k8s_json_patch module instead.",
+ version="3.0.0",
+ collection_name="kubernetes.core",
+ )
+ try:
+ params = dict(name=name, namespace=namespace)
+ if self.check_mode:
+ params["dry_run"] = "All"
+ if merge_type:
+ params["content_type"] = "application/{0}-patch+json".format(merge_type)
+ k8s_obj = resource.patch(definition, **params).to_dict()
+ match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
+ error = {}
+ return k8s_obj, {}
+ except DynamicApiError as exc:
+ msg = "Failed to patch object: {0}".format(exc.body)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ error = dict(
+ msg=msg,
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ warnings=self.warnings,
+ )
+ return None, error
+ except Exception as exc:
+ msg = "Failed to patch object: {0}".format(exc)
+ if self.warnings:
+ msg += "\n" + "\n ".join(self.warnings)
+ error = dict(
+ msg=msg,
+ error=to_native(exc),
+ status="",
+ reason="",
+ warnings=self.warnings,
+ )
+ return None, error
+
+ def create_project_request(self, definition):
+ definition["kind"] = "ProjectRequest"
+ result = {"changed": False, "result": {}}
+ resource = self.find_resource(
+ "ProjectRequest", definition["apiVersion"], fail=True
+ )
+ if not self.check_mode:
+ try:
+ k8s_obj = resource.create(definition)
+ result["result"] = k8s_obj.to_dict()
+ except DynamicApiError as exc:
+ self.fail_json(
+ msg="Failed to create object: {0}".format(exc.body),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ result["changed"] = True
+ result["method"] = "create"
+ return result
+
+
+def _encode_stringdata(definition):
+ if definition["kind"] == "Secret" and "stringData" in definition:
+ for k, v in definition["stringData"].items():
+ encoded = base64.b64encode(to_bytes(v))
+ definition.setdefault("data", {})[k] = to_text(encoded)
+ del definition["stringData"]
+ return definition
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/copy.py b/ansible_collections/kubernetes/core/plugins/module_utils/copy.py
new file mode 100644
index 00000000..c7e1b4e2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/copy.py
@@ -0,0 +1,444 @@
+# Copyright [2021] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import os
+from tempfile import TemporaryFile, NamedTemporaryFile
+from select import select
+from abc import ABCMeta, abstractmethod
+import tarfile
+
+# from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible.module_utils._text import to_native
+
+try:
+ from kubernetes.client.api import core_v1_api
+ from kubernetes.stream import stream
+ from kubernetes.stream.ws_client import (
+ STDOUT_CHANNEL,
+ STDERR_CHANNEL,
+ ERROR_CHANNEL,
+ ABNF,
+ )
+except ImportError:
+ pass
+
+try:
+ import yaml
+except ImportError:
+ # ImportError are managed by the common module already.
+ pass
+
+
+class K8SCopy(metaclass=ABCMeta):
+ def __init__(self, module, client):
+ self.client = client
+ self.module = module
+ self.api_instance = core_v1_api.CoreV1Api(client.client)
+
+ self.local_path = module.params.get("local_path")
+ self.name = module.params.get("pod")
+ self.namespace = module.params.get("namespace")
+ self.remote_path = module.params.get("remote_path")
+ self.content = module.params.get("content")
+
+ self.no_preserve = module.params.get("no_preserve")
+ self.container_arg = {}
+ if module.params.get("container"):
+ self.container_arg["container"] = module.params.get("container")
+ self.check_mode = self.module.check_mode
+
+ def _run_from_pod(self, cmd):
+ try:
+ resp = stream(
+ self.api_instance.connect_get_namespaced_pod_exec,
+ self.name,
+ self.namespace,
+ command=cmd,
+ async_req=False,
+ stderr=True,
+ stdin=False,
+ stdout=True,
+ tty=False,
+ _preload_content=False,
+ **self.container_arg,
+ )
+
+ stderr, stdout = [], []
+ while resp.is_open():
+ resp.update(timeout=1)
+ if resp.peek_stdout():
+ stdout.extend(resp.read_stdout().rstrip("\n").split("\n"))
+ if resp.peek_stderr():
+ stderr.extend(resp.read_stderr().rstrip("\n").split("\n"))
+ error = resp.read_channel(ERROR_CHANNEL)
+ resp.close()
+ error = yaml.safe_load(error)
+ return error, stdout, stderr
+ except Exception as e:
+ self.module.fail_json(
+ msg="Error while running/parsing from pod {1}/{2} command='{0}' : {3}".format(
+ self.namespace, self.name, cmd, to_native(e)
+ )
+ )
+
+ def is_directory_path_from_pod(self, file_path, failed_if_not_exists=True):
+ # check if file exists
+ error, out, err = self._run_from_pod(cmd=["test", "-e", file_path])
+ if error.get("status") != "Success":
+ if failed_if_not_exists:
+ return None, "%s does not exist in remote pod filesystem" % file_path
+ return False, None
+ error, out, err = self._run_from_pod(cmd=["test", "-d", file_path])
+ return error.get("status") == "Success", None
+
+ @abstractmethod
+ def run(self):
+ pass
+
+
+class K8SCopyFromPod(K8SCopy):
+ """
+ Copy files/directory from Pod into local filesystem
+ """
+
+ def __init__(self, module, client):
+ super(K8SCopyFromPod, self).__init__(module, client)
+ self.is_remote_path_dir = None
+ self.files_to_copy = []
+ self._shellname = None
+
+ @property
+ def pod_shell(self):
+ if self._shellname is None:
+ for s in ("/bin/sh", "/bin/bash"):
+ error, out, err = self._run_from_pod(s)
+ if error.get("status") == "Success":
+ self._shellname = s
+ break
+ return self._shellname
+
+ def listfiles_with_find(self, path):
+ find_cmd = ["find", path, "-type", "f"]
+ error, files, err = self._run_from_pod(cmd=find_cmd)
+ if error.get("status") != "Success":
+ self.module.fail_json(msg=error.get("message"))
+ return files
+
+ def listfile_with_echo(self, path):
+ echo_cmd = [
+ self.pod_shell,
+ "-c",
+ "echo {path}/* {path}/.*".format(
+ path=path.translate(str.maketrans({" ": r"\ "}))
+ ),
+ ]
+ error, out, err = self._run_from_pod(cmd=echo_cmd)
+ if error.get("status") != "Success":
+ self.module.fail_json(msg=error.get("message"))
+
+ files = []
+ if out:
+ output = out[0] + " "
+ files = [
+ os.path.join(path, p[:-1])
+ for p in output.split(f"{path}/")
+ if p and p[:-1] not in (".", "..")
+ ]
+
+ result = []
+ for f in files:
+ is_dir, err = self.is_directory_path_from_pod(f)
+ if err:
+ continue
+ if not is_dir:
+ result.append(f)
+ continue
+ result += self.listfile_with_echo(f)
+ return result
+
+ def list_remote_files(self):
+ """
+ This method will check if the remote path is a dir or file
+ if it is a directory the file list will be updated accordingly
+ """
+ # check is remote path exists and is a file or directory
+ is_dir, error = self.is_directory_path_from_pod(self.remote_path)
+ if error:
+ self.module.fail_json(msg=error)
+
+ if not is_dir:
+ return [self.remote_path]
+ else:
+ # find executable to list dir with
+ executables = dict(
+ find=self.listfiles_with_find,
+ echo=self.listfile_with_echo,
+ )
+ for item in executables:
+ error, out, err = self._run_from_pod(item)
+ if error.get("status") == "Success":
+ return executables.get(item)(self.remote_path)
+
+ def read(self):
+ self.stdout = None
+ self.stderr = None
+
+ if self.response.is_open():
+ if not self.response.sock.connected:
+ self.response._connected = False
+ else:
+ ret, out, err = select((self.response.sock.sock,), (), (), 0)
+ if ret:
+ code, frame = self.response.sock.recv_data_frame(True)
+ if code == ABNF.OPCODE_CLOSE:
+ self.response._connected = False
+ elif (
+ code in (ABNF.OPCODE_BINARY, ABNF.OPCODE_TEXT)
+ and len(frame.data) > 1
+ ):
+ channel = frame.data[0]
+ content = frame.data[1:]
+ if content:
+ if channel == STDOUT_CHANNEL:
+ self.stdout = content
+ elif channel == STDERR_CHANNEL:
+ self.stderr = content.decode("utf-8", "replace")
+
+ def copy(self):
+ is_remote_path_dir = (
+ len(self.files_to_copy) > 1 or self.files_to_copy[0] != self.remote_path
+ )
+ relpath_start = self.remote_path
+ if is_remote_path_dir and os.path.isdir(self.local_path):
+ relpath_start = os.path.dirname(self.remote_path)
+
+ if not self.check_mode:
+ for remote_file in self.files_to_copy:
+ dest_file = self.local_path
+ if is_remote_path_dir:
+ dest_file = os.path.join(
+ self.local_path,
+ os.path.relpath(remote_file, start=relpath_start),
+ )
+ # create directory to copy file in
+ os.makedirs(os.path.dirname(dest_file), exist_ok=True)
+
+ pod_command = ["cat", remote_file]
+ self.response = stream(
+ self.api_instance.connect_get_namespaced_pod_exec,
+ self.name,
+ self.namespace,
+ command=pod_command,
+ stderr=True,
+ stdin=True,
+ stdout=True,
+ tty=False,
+ _preload_content=False,
+ **self.container_arg,
+ )
+ errors = []
+ with open(dest_file, "wb") as fh:
+ while self.response._connected:
+ self.read()
+ if self.stdout:
+ fh.write(self.stdout)
+ if self.stderr:
+ errors.append(self.stderr)
+ if errors:
+ self.module.fail_json(
+ msg="Failed to copy file from Pod: {0}".format("".join(errors))
+ )
+ self.module.exit_json(
+ changed=True,
+ result="{0} successfully copied locally into {1}".format(
+ self.remote_path, self.local_path
+ ),
+ )
+
+ def run(self):
+ self.files_to_copy = self.list_remote_files()
+ if self.files_to_copy == []:
+ self.module.exit_json(
+ changed=False,
+ warning="No file found from directory '{0}' into remote Pod.".format(
+ self.remote_path
+ ),
+ )
+ self.copy()
+
+
+class K8SCopyToPod(K8SCopy):
+ """
+ Copy files/directory from local filesystem into remote Pod
+ """
+
+ def __init__(self, module, client):
+ super(K8SCopyToPod, self).__init__(module, client)
+ self.files_to_copy = list()
+
+ def close_temp_file(self):
+ if self.named_temp_file:
+ self.named_temp_file.close()
+
+ def run(self):
+ # remove trailing slash from destination path
+ dest_file = self.remote_path.rstrip("/")
+ src_file = self.local_path
+ self.named_temp_file = None
+ if self.content:
+ self.named_temp_file = NamedTemporaryFile(mode="w")
+ self.named_temp_file.write(self.content)
+ self.named_temp_file.flush()
+ src_file = self.named_temp_file.name
+ else:
+ if not os.path.exists(self.local_path):
+ self.module.fail_json(
+ msg="{0} does not exist in local filesystem".format(self.local_path)
+ )
+ if not os.access(self.local_path, os.R_OK):
+ self.module.fail_json(msg="{0} not readable".format(self.local_path))
+
+ is_dir, err = self.is_directory_path_from_pod(
+ self.remote_path, failed_if_not_exists=False
+ )
+ if err:
+ self.module.fail_json(msg=err)
+ if is_dir:
+ if self.content:
+ self.module.fail_json(
+ msg="When content is specified, remote path should not be an existing directory"
+ )
+ else:
+ dest_file = os.path.join(dest_file, os.path.basename(src_file))
+
+ if not self.check_mode:
+ if self.no_preserve:
+ tar_command = [
+ "tar",
+ "--no-same-permissions",
+ "--no-same-owner",
+ "-xmf",
+ "-",
+ ]
+ else:
+ tar_command = ["tar", "-xmf", "-"]
+
+ if dest_file.startswith("/"):
+ tar_command.extend(["-C", "/"])
+
+ response = stream(
+ self.api_instance.connect_get_namespaced_pod_exec,
+ self.name,
+ self.namespace,
+ command=tar_command,
+ stderr=True,
+ stdin=True,
+ stdout=True,
+ tty=False,
+ _preload_content=False,
+ **self.container_arg,
+ )
+ with TemporaryFile() as tar_buffer:
+ with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
+ tar.add(src_file, dest_file)
+ tar_buffer.seek(0)
+ commands = []
+ # push command in chunk mode
+ size = 1024 * 1024
+ while True:
+ data = tar_buffer.read(size)
+ if not data:
+ break
+ commands.append(data)
+
+ stderr, stdout = [], []
+ while response.is_open():
+ if response.peek_stdout():
+ stdout.append(response.read_stdout().rstrip("\n"))
+ if response.peek_stderr():
+ stderr.append(response.read_stderr().rstrip("\n"))
+ if commands:
+ cmd = commands.pop(0)
+ response.write_stdin(cmd)
+ else:
+ break
+ response.close()
+ if stderr:
+ self.close_temp_file()
+ self.module.fail_json(
+ command=tar_command,
+ msg="Failed to copy local file/directory into Pod due to: {0}".format(
+ "".join(stderr)
+ ),
+ )
+ self.close_temp_file()
+ if self.content:
+ self.module.exit_json(
+ changed=True,
+ result="Content successfully copied into {0} on remote Pod".format(
+ self.remote_path
+ ),
+ )
+ self.module.exit_json(
+ changed=True,
+ result="{0} successfully copied into remote Pod into {1}".format(
+ self.local_path, self.remote_path
+ ),
+ )
+
+
+def check_pod(svc):
+ module = svc.module
+ namespace = module.params.get("namespace")
+ name = module.params.get("pod")
+ container = module.params.get("container")
+
+ try:
+ resource = svc.find_resource("Pod", None, True)
+ except CoreException as e:
+ module.fail_json(msg=to_native(e))
+
+ def _fail(exc):
+ arg = {}
+ if hasattr(exc, "body"):
+ msg = (
+ "Namespace={0} Kind=Pod Name={1}: Failed requested object: {2}".format(
+ namespace, name, exc.body
+ )
+ )
+ else:
+ msg = to_native(exc)
+ for attr in ["status", "reason"]:
+ if hasattr(exc, attr):
+ arg[attr] = getattr(exc, attr)
+ module.fail_json(msg=msg, **arg)
+
+ try:
+ result = svc.client.get(resource, name=name, namespace=namespace)
+ containers = [
+ c["name"] for c in result.to_dict()["status"]["containerStatuses"]
+ ]
+ if container and container not in containers:
+ module.fail_json(msg="Pod has no container {0}".format(container))
+ return containers
+ except Exception as exc:
+ _fail(exc)
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/exceptions.py b/ansible_collections/kubernetes/core/plugins/module_utils/exceptions.py
new file mode 100644
index 00000000..ef6d5fdc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/exceptions.py
@@ -0,0 +1,22 @@
+# Copyright [2017] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ApplyException(Exception):
+ """Could not apply patch"""
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/hashes.py b/ansible_collections/kubernetes/core/plugins/module_utils/hashes.py
new file mode 100644
index 00000000..3d44a7d9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/hashes.py
@@ -0,0 +1,78 @@
+# Copyright [2017] [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.
+
+# Implement ConfigMapHash and SecretHash equivalents
+# Based on https://github.com/kubernetes/kubernetes/pull/49961
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import json
+import hashlib
+
+try:
+ import string
+
+ maketrans = string.maketrans
+except AttributeError:
+ maketrans = str.maketrans
+
+try:
+ from collections import OrderedDict
+except ImportError:
+ from orderreddict import OrderedDict
+
+
+def sorted_dict(unsorted_dict):
+ result = OrderedDict()
+ for (k, v) in sorted(unsorted_dict.items()):
+ if isinstance(v, dict):
+ v = sorted_dict(v)
+ result[k] = v
+ return result
+
+
+def generate_hash(resource):
+ # Get name from metadata
+ metada = resource.get("metadata", {})
+ key = "name"
+ resource["name"] = metada.get("name", "")
+ generate_name = metada.get("generateName", "")
+ if resource["name"] == "" and generate_name:
+ del resource["name"]
+ key = "generateName"
+ resource["generateName"] = generate_name
+ if resource["kind"] == "ConfigMap":
+ marshalled = marshal(sorted_dict(resource), ["data", "kind", key])
+ del resource[key]
+ return encode(marshalled)
+ if resource["kind"] == "Secret":
+ marshalled = marshal(sorted_dict(resource), ["data", "kind", key, "type"])
+ del resource[key]
+ return encode(marshalled)
+ raise NotImplementedError
+
+
+def marshal(data, keys):
+ ordered = OrderedDict()
+ for key in keys:
+ ordered[key] = data.get(key, "")
+ return json.dumps(ordered, separators=(",", ":")).encode("utf-8")
+
+
+def encode(resource):
+ return (
+ hashlib.sha256(resource).hexdigest()[:10].translate(maketrans("013ae", "ghkmt"))
+ )
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/helm.py b/ansible_collections/kubernetes/core/plugins/module_utils/helm.py
new file mode 100644
index 00000000..a7a2fa7c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/helm.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+import os
+import tempfile
+import traceback
+import re
+import json
+import copy
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible.module_utils.six import string_types
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+from ansible.module_utils.basic import AnsibleModule
+
+try:
+ import yaml
+
+ HAS_YAML = True
+ YAML_IMP_ERR = None
+except ImportError:
+ YAML_IMP_ERR = traceback.format_exc()
+ HAS_YAML = False
+
+
+def parse_helm_plugin_list(output=None):
+ """
+ Parse `helm plugin list`, return list of plugins
+ """
+ ret = []
+ if not output:
+ return ret
+
+ for line in output:
+ if line.startswith("NAME"):
+ continue
+ name, version, description = line.split("\t", 3)
+ name = name.strip()
+ version = version.strip()
+ description = description.strip()
+ if name == "":
+ continue
+ ret.append((name, version, description))
+
+ return ret
+
+
+def write_temp_kubeconfig(server, validate_certs=True, ca_cert=None, kubeconfig=None):
+ # Workaround until https://github.com/helm/helm/pull/8622 is merged
+ content = {
+ "apiVersion": "v1",
+ "kind": "Config",
+ "clusters": [{"cluster": {"server": server}, "name": "generated-cluster"}],
+ "contexts": [
+ {"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
+ ],
+ "current-context": "generated-context",
+ }
+ if kubeconfig:
+ content = copy.deepcopy(kubeconfig)
+
+ for cluster in content["clusters"]:
+ if server:
+ cluster["cluster"]["server"] = server
+ if not validate_certs:
+ cluster["cluster"]["insecure-skip-tls-verify"] = True
+ if ca_cert:
+ cluster["cluster"]["certificate-authority"] = ca_cert
+ return content
+
+
+class AnsibleHelmModule(object):
+
+ """
+ An Ansible module class for Kubernetes.core helm modules
+ """
+
+ def __init__(self, **kwargs):
+
+ self._module = None
+ if "module" in kwargs:
+ self._module = kwargs.get("module")
+ else:
+ self._module = AnsibleModule(**kwargs)
+
+ self.helm_env = None
+
+ def __getattr__(self, name):
+ return getattr(self._module, name)
+
+ @property
+ def params(self):
+ return self._module.params
+
+ def _prepare_helm_environment(self):
+ param_to_env_mapping = [
+ ("context", "HELM_KUBECONTEXT"),
+ ("release_namespace", "HELM_NAMESPACE"),
+ ("api_key", "HELM_KUBETOKEN"),
+ ("host", "HELM_KUBEAPISERVER"),
+ ]
+
+ env_update = {}
+ for p, env in param_to_env_mapping:
+ if self.params.get(p):
+ env_update[env] = self.params.get(p)
+
+ kubeconfig_content = None
+ kubeconfig = self.params.get("kubeconfig")
+ if kubeconfig:
+ if isinstance(kubeconfig, string_types):
+ with open(kubeconfig) as fd:
+ kubeconfig_content = yaml.safe_load(fd)
+ elif isinstance(kubeconfig, dict):
+ kubeconfig_content = kubeconfig
+
+ if self.params.get("ca_cert"):
+ ca_cert = self.params.get("ca_cert")
+ if LooseVersion(self.get_helm_version()) < LooseVersion("3.5.0"):
+ # update certs from kubeconfig
+ kubeconfig_content = write_temp_kubeconfig(
+ server=self.params.get("host"),
+ ca_cert=ca_cert,
+ kubeconfig=kubeconfig_content,
+ )
+ else:
+ env_update["HELM_KUBECAFILE"] = ca_cert
+
+ if self.params.get("validate_certs") is False:
+ validate_certs = self.params.get("validate_certs")
+ if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
+ # update certs from kubeconfig
+ kubeconfig_content = write_temp_kubeconfig(
+ server=self.params.get("host"),
+ validate_certs=validate_certs,
+ kubeconfig=kubeconfig_content,
+ )
+ else:
+ env_update["HELM_KUBEINSECURE_SKIP_TLS_VERIFY"] = "true"
+
+ if kubeconfig_content:
+ fd, kubeconfig_path = tempfile.mkstemp()
+ with os.fdopen(fd, "w") as fp:
+ json.dump(kubeconfig_content, fp)
+
+ env_update["KUBECONFIG"] = kubeconfig_path
+ self.add_cleanup_file(kubeconfig_path)
+
+ return env_update
+
+ @property
+ def env_update(self):
+ if self.helm_env is None:
+ self.helm_env = self._prepare_helm_environment()
+ return self.helm_env
+
+ def run_helm_command(self, command, fails_on_error=True):
+ if not HAS_YAML:
+ self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
+
+ rc, out, err = self.run_command(command, environ_update=self.env_update)
+ if fails_on_error and rc != 0:
+ self.fail_json(
+ msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
+ rc, out, err
+ ),
+ stdout=out,
+ stderr=err,
+ command=command,
+ )
+ return rc, out, err
+
+ def get_helm_binary(self):
+ return self.params.get("binary_path") or self.get_bin_path(
+ "helm", required=True
+ )
+
+ def get_helm_version(self):
+
+ command = self.get_helm_binary() + " version"
+ rc, out, err = self.run_command(command)
+ m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
+ if m:
+ return m.group(1)
+ m = re.match(r'Client: &version.Version{SemVer:"v([0-9\.]*)", ', out)
+ if m:
+ return m.group(1)
+ return None
+
+ def get_values(self, release_name, get_all=False):
+ """
+ Get Values from deployed release
+ """
+ if not HAS_YAML:
+ self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
+
+ get_command = (
+ self.get_helm_binary() + " get values --output=yaml " + release_name
+ )
+
+ if get_all:
+ get_command += " -a"
+
+ rc, out, err = self.run_helm_command(get_command)
+ # Helm 3 return "null" string when no values are set
+ if out.rstrip("\n") == "null":
+ return {}
+ return yaml.safe_load(out)
+
+ def parse_yaml_content(self, content):
+
+ if not HAS_YAML:
+ self.fail_json(msg=missing_required_lib("yaml"), exception=HAS_YAML)
+
+ try:
+ return list(yaml.safe_load_all(content))
+ except (IOError, yaml.YAMLError) as exc:
+ self.fail_json(
+ msg="Error parsing YAML content: {0}".format(exc), raw_data=content
+ )
+
+ def get_manifest(self, release_name):
+
+ command = [
+ self.get_helm_binary(),
+ "get",
+ "manifest",
+ release_name,
+ ]
+ rc, out, err = self.run_helm_command(" ".join(command))
+ if rc != 0:
+ self.fail_json(msg=err)
+ return self.parse_yaml_content(out)
+
+ def get_notes(self, release_name):
+
+ command = [
+ self.get_helm_binary(),
+ "get",
+ "notes",
+ release_name,
+ ]
+ rc, out, err = self.run_helm_command(" ".join(command))
+ if rc != 0:
+ self.fail_json(msg=err)
+ return out
+
+ def get_hooks(self, release_name):
+ command = [
+ self.get_helm_binary(),
+ "get",
+ "hooks",
+ release_name,
+ ]
+ rc, out, err = self.run_helm_command(" ".join(command))
+ if rc != 0:
+ self.fail_json(msg=err)
+ return self.parse_yaml_content(out)
+
+ def get_helm_plugin_list(self):
+ """
+ Return `helm plugin list`
+ """
+ helm_plugin_list = self.get_helm_binary() + " plugin list"
+ rc, out, err = self.run_helm_command(helm_plugin_list)
+ if rc != 0 or (out == "" and err == ""):
+ self.fail_json(
+ msg="Failed to get Helm plugin info",
+ command=helm_plugin_list,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ return (rc, out, err, helm_plugin_list)
+
+ def get_helm_set_values_args(self, set_values):
+ if any(v.get("value_type") == "json" for v in set_values):
+ if LooseVersion(self.get_helm_version()) < LooseVersion("3.10.0"):
+ self.fail_json(
+ msg="This module requires helm >= 3.10.0, to use set_values parameter with value type set to 'json'. current version is {0}".format(
+ self.get_helm_version()
+ )
+ )
+
+ options = []
+ for opt in set_values:
+ value_type = opt.get("value_type", "raw")
+ value = opt.get("value")
+
+ if value_type == "raw":
+ options.append("--set " + value)
+ else:
+ options.append("--set-{0} '{1}'".format(value_type, value))
+
+ return " ".join(options)
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/helm_args_common.py b/ansible_collections/kubernetes/core/plugins/module_utils/helm_args_common.py
new file mode 100644
index 00000000..ebf8e9f5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/helm_args_common.py
@@ -0,0 +1,42 @@
+from __future__ import absolute_import, division, print_function
+
+from ansible.module_utils.basic import env_fallback
+
+__metaclass__ = type
+
+
+HELM_AUTH_ARG_SPEC = dict(
+ binary_path=dict(type="path"),
+ context=dict(
+ type="str",
+ aliases=["kube_context"],
+ fallback=(env_fallback, ["K8S_AUTH_CONTEXT"]),
+ ),
+ kubeconfig=dict(
+ type="raw",
+ aliases=["kubeconfig_path"],
+ fallback=(env_fallback, ["K8S_AUTH_KUBECONFIG"]),
+ ),
+ host=dict(type="str", fallback=(env_fallback, ["K8S_AUTH_HOST"])),
+ ca_cert=dict(
+ type="path",
+ aliases=["ssl_ca_cert"],
+ fallback=(env_fallback, ["K8S_AUTH_SSL_CA_CERT"]),
+ ),
+ validate_certs=dict(
+ type="bool",
+ default=True,
+ aliases=["verify_ssl"],
+ fallback=(env_fallback, ["K8S_AUTH_VERIFY_SSL"]),
+ ),
+ api_key=dict(
+ type="str",
+ no_log=True,
+ fallback=(env_fallback, ["K8S_AUTH_API_KEY"]),
+ ),
+)
+
+HELM_AUTH_MUTUALLY_EXCLUSIVE = [
+ ("context", "ca_cert"),
+ ("context", "validate_certs"),
+]
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/client.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/client.py
new file mode 100644
index 00000000..2589e560
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/client.py
@@ -0,0 +1,368 @@
+# Copyright: (c) 2021, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+import os
+import hashlib
+from typing import Any, Dict, List, Optional
+
+from ansible.module_utils.six import iteritems, string_types
+
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_MAP,
+ AUTH_ARG_SPEC,
+ AUTH_PROXY_HEADERS_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ requires as _requires,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils import (
+ k8sdynamicclient,
+ )
+ from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import (
+ LazyDiscoverer,
+ )
+except ImportError:
+ # Handled in module setup
+ pass
+
+try:
+ import kubernetes
+ from kubernetes.dynamic.exceptions import (
+ ResourceNotFoundError,
+ ResourceNotUniqueError,
+ )
+ from kubernetes.dynamic.resource import Resource
+except ImportError:
+ # kubernetes import error is handled in module setup
+ # This is defined only for the sake of Ansible's checked import requirement
+ Resource = Any # type: ignore
+
+try:
+ import urllib3
+
+ urllib3.disable_warnings()
+except ImportError:
+ # Handled in module setup
+ pass
+
+
+_pool = {}
+
+
+class unique_string(str):
+ _low = None
+
+ def __hash__(self):
+ return id(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def lower(self):
+ if self._low is None:
+ lower = str.lower(self)
+ if str.__eq__(lower, self):
+ self._low = self
+ else:
+ self._low = unique_string(lower)
+ return self._low
+
+
+def _create_auth_spec(module=None, **kwargs) -> Dict:
+ auth: Dict = {}
+ # If authorization variables aren't defined, look for them in environment variables
+ for true_name, arg_name in AUTH_ARG_MAP.items():
+ if module and module.params.get(arg_name) is not None:
+ auth[true_name] = module.params.get(arg_name)
+ elif arg_name in kwargs and kwargs.get(arg_name) is not None:
+ auth[true_name] = kwargs.get(arg_name)
+ elif true_name in kwargs and kwargs.get(true_name) is not None:
+ # Aliases in kwargs
+ auth[true_name] = kwargs.get(true_name)
+ elif arg_name == "proxy_headers":
+ # specific case for 'proxy_headers' which is a dictionary
+ proxy_headers = {}
+ for key in AUTH_PROXY_HEADERS_SPEC.keys():
+ env_value = os.getenv(
+ "K8S_AUTH_PROXY_HEADERS_{0}".format(key.upper()), None
+ )
+ if env_value is not None:
+ if AUTH_PROXY_HEADERS_SPEC[key].get("type") == "bool":
+ env_value = env_value.lower() not in ["0", "false", "no"]
+ proxy_headers[key] = env_value
+ if proxy_headers is not {}:
+ auth[true_name] = proxy_headers
+ else:
+ env_value = os.getenv(
+ "K8S_AUTH_{0}".format(arg_name.upper()), None
+ ) or os.getenv("K8S_AUTH_{0}".format(true_name.upper()), None)
+ if env_value is not None:
+ if AUTH_ARG_SPEC[arg_name].get("type") == "bool":
+ env_value = env_value.lower() not in ["0", "false", "no"]
+ auth[true_name] = env_value
+
+ return auth
+
+
+def _load_config(auth: Dict) -> None:
+ kubeconfig = auth.get("kubeconfig")
+ optional_arg = {
+ "context": auth.get("context"),
+ "persist_config": auth.get("persist_config"),
+ }
+ if kubeconfig:
+ if isinstance(kubeconfig, string_types):
+ kubernetes.config.load_kube_config(config_file=kubeconfig, **optional_arg)
+ elif isinstance(kubeconfig, dict):
+ kubernetes.config.load_kube_config_from_dict(
+ config_dict=kubeconfig, **optional_arg
+ )
+ else:
+ kubernetes.config.load_kube_config(config_file=None, **optional_arg)
+
+
+def _create_configuration(auth: Dict):
+ def auth_set(*names: list) -> bool:
+ return all(auth.get(name) for name in names)
+
+ if auth_set("host"):
+ # Removing trailing slashes if any from hostname
+ auth["host"] = auth.get("host").rstrip("/")
+
+ if (
+ auth_set("username", "password", "host")
+ or auth_set("api_key", "host")
+ or auth_set("cert_file", "key_file", "host")
+ ):
+ # We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
+ pass
+ elif auth_set("kubeconfig") or auth_set("context"):
+ try:
+ _load_config(auth)
+ except Exception as err:
+ raise err
+
+ else:
+ # First try to do incluster config, then kubeconfig
+ try:
+ kubernetes.config.load_incluster_config()
+ except kubernetes.config.ConfigException:
+ try:
+ _load_config(auth)
+ except Exception as err:
+ raise err
+
+ # Override any values in the default configuration with Ansible parameters
+ # As of kubernetes-client v12.0.0, get_default_copy() is required here
+ try:
+ configuration = kubernetes.client.Configuration().get_default_copy()
+ except AttributeError:
+ configuration = kubernetes.client.Configuration()
+
+ for key, value in iteritems(auth):
+ if key in AUTH_ARG_MAP.keys() and value is not None:
+ if key == "api_key":
+ setattr(
+ configuration, key, {"authorization": "Bearer {0}".format(value)}
+ )
+ elif key == "proxy_headers":
+ headers = urllib3.util.make_headers(**value)
+ setattr(configuration, key, headers)
+ else:
+ setattr(configuration, key, value)
+
+ return configuration
+
+
+def _create_headers(module=None, **kwargs):
+ header_map = {
+ "impersonate_user": "Impersonate-User",
+ "impersonate_groups": "Impersonate-Group",
+ }
+
+ headers = {}
+ for arg_name, header_name in header_map.items():
+ value = None
+ if module and module.params.get(arg_name) is not None:
+ value = module.params.get(arg_name)
+ elif arg_name in kwargs and kwargs.get(arg_name) is not None:
+ value = kwargs.get(arg_name)
+ else:
+ value = os.getenv("K8S_AUTH_{0}".format(arg_name.upper()), None)
+ if value is not None:
+ if AUTH_ARG_SPEC[arg_name].get("type") == "list":
+ value = [x for x in value.split(",") if x != ""]
+ if value:
+ headers[header_name] = value
+ return headers
+
+
+def _configuration_digest(configuration, **kwargs) -> str:
+ m = hashlib.sha256()
+ for k in AUTH_ARG_MAP:
+ if not hasattr(configuration, k):
+ v = None
+ else:
+ v = getattr(configuration, k)
+ if v and k in ["ssl_ca_cert", "cert_file", "key_file"]:
+ with open(str(v), "r") as fd:
+ content = fd.read()
+ m.update(content.encode())
+ else:
+ m.update(str(v).encode())
+ for k, v in kwargs.items():
+ content = "{0}: {1}".format(k, v)
+ m.update(content.encode())
+ digest = m.hexdigest()
+
+ return digest
+
+
+def _set_header(client, header, value):
+ if isinstance(value, list):
+ for v in value:
+ client.set_default_header(header_name=unique_string(header), header_value=v)
+ else:
+ client.set_default_header(header_name=header, header_value=value)
+
+
+def cache(func):
+ def wrapper(*args, **kwargs):
+ client = None
+ hashable_kwargs = {}
+ for k, v in kwargs.items():
+ if isinstance(v, list):
+ hashable_kwargs[k] = ",".join(sorted(v))
+ else:
+ hashable_kwargs[k] = v
+ digest = _configuration_digest(*args, **hashable_kwargs)
+ if digest in _pool:
+ client = _pool[digest]
+ else:
+ client = func(*args, **kwargs)
+ _pool[digest] = client
+
+ return client
+
+ return wrapper
+
+
+@cache
+def create_api_client(configuration, **headers):
+ client = kubernetes.client.ApiClient(configuration)
+ for header, value in headers.items():
+ _set_header(client, header, value)
+ return k8sdynamicclient.K8SDynamicClient(client, discoverer=LazyDiscoverer)
+
+
+class K8SClient:
+ """A Client class for K8S modules.
+
+ This class has the primary purpose to proxy the kubernetes client and resource objects.
+ If there is a need for other methods or attributes to be proxied, they can be added here.
+ """
+
+ K8S_SERVER_DRY_RUN = "All"
+
+ def __init__(self, configuration, client, dry_run: bool = False) -> None:
+ self.configuration = configuration
+ self.client = client
+ self.dry_run = dry_run
+
+ @property
+ def resources(self) -> List[Any]:
+ return self.client.resources
+
+ def _find_resource_with_prefix(
+ self, prefix: str, kind: str, api_version: str
+ ) -> Resource:
+ for attribute in ["kind", "name", "singular_name"]:
+ try:
+ return self.client.resources.get(
+ **{"prefix": prefix, "api_version": api_version, attribute: kind}
+ )
+ except (ResourceNotFoundError, ResourceNotUniqueError):
+ pass
+ return self.client.resources.get(
+ prefix=prefix, api_version=api_version, short_names=[kind]
+ )
+
+ def resource(self, kind: str, api_version: str) -> Resource:
+ """Fetch a kubernetes client resource.
+
+ This will attempt to find a kubernetes resource trying, in order, kind,
+ name, singular_name and short_names.
+ """
+ try:
+ if api_version == "v1":
+ return self._find_resource_with_prefix("api", kind, api_version)
+ except ResourceNotFoundError:
+ pass
+ return self._find_resource_with_prefix(None, kind, api_version)
+
+ def _ensure_dry_run(self, params: Dict) -> Dict:
+ if self.dry_run:
+ params["dry_run"] = self.K8S_SERVER_DRY_RUN
+ return params
+
+ def validate(
+ self, resource, version: Optional[str] = None, strict: Optional[bool] = False
+ ):
+ return self.client.validate(resource, version, strict)
+
+ def get(self, resource, **params):
+ return resource.get(**params)
+
+ def delete(self, resource, **params):
+ return resource.delete(**self._ensure_dry_run(params))
+
+ def apply(self, resource, definition, namespace, **params):
+ return resource.apply(
+ definition, namespace=namespace, **self._ensure_dry_run(params)
+ )
+
+ def create(self, resource, definition, **params):
+ return resource.create(definition, **self._ensure_dry_run(params))
+
+ def replace(self, resource, definition, **params):
+ return resource.replace(definition, **self._ensure_dry_run(params))
+
+ def patch(self, resource, definition, **params):
+ return resource.patch(definition, **self._ensure_dry_run(params))
+
+
+def get_api_client(module=None, **kwargs: Optional[Any]) -> K8SClient:
+ auth_spec = _create_auth_spec(module, **kwargs)
+ if module:
+ requires = module.requires
+ else:
+ requires = _requires
+ if isinstance(auth_spec.get("kubeconfig"), dict):
+ requires("kubernetes", "17.17.0", "to use in-memory config")
+ if auth_spec.get("no_proxy"):
+ requires("kubernetes", "19.15.0", "to use the no_proxy feature")
+
+ try:
+ configuration = _create_configuration(auth_spec)
+ headers = _create_headers(module, **kwargs)
+ client = create_api_client(configuration, **headers)
+ except kubernetes.config.ConfigException as e:
+ msg = "Could not create API client: {0}".format(e)
+ raise CoreException(msg) from e
+
+ dry_run = False
+ if module and module.server_side_dry_run:
+ dry_run = True
+
+ k8s_client = K8SClient(
+ configuration=configuration,
+ client=client,
+ dry_run=dry_run,
+ )
+
+ return k8s_client
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/core.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/core.py
new file mode 100644
index 00000000..131e80e2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/core.py
@@ -0,0 +1,175 @@
+import traceback
+
+from typing import Optional
+
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.basic import missing_required_lib
+from ansible.module_utils.common.text.converters import to_text
+
+
+class AnsibleK8SModule:
+ """A base module class for K8S modules.
+
+ This class should be used instead of directly using AnsibleModule. If there
+ is a need for other methods or attributes to be proxied, they can be added
+ here.
+ """
+
+ default_settings = {
+ "check_k8s": True,
+ "check_pyyaml": True,
+ "module_class": AnsibleModule,
+ }
+
+ def __init__(self, **kwargs) -> None:
+ local_settings = {}
+ for key in AnsibleK8SModule.default_settings:
+ try:
+ local_settings[key] = kwargs.pop(key)
+ except KeyError:
+ local_settings[key] = AnsibleK8SModule.default_settings[key]
+ self.settings = local_settings
+
+ self._module = self.settings["module_class"](**kwargs)
+
+ if self.settings["check_k8s"]:
+ self.requires("kubernetes")
+ self.has_at_least("kubernetes", "12.0.0", warn=True)
+
+ if self.settings["check_pyyaml"]:
+ self.requires("pyyaml")
+
+ @property
+ def check_mode(self):
+ return self._module.check_mode
+
+ @property
+ def server_side_dry_run(self):
+ return self.check_mode and self.has_at_least("kubernetes", "18.20.0")
+
+ @property
+ def _diff(self):
+ return self._module._diff
+
+ @property
+ def _name(self):
+ return self._module._name
+
+ @property
+ def params(self):
+ return self._module.params
+
+ def warn(self, *args, **kwargs):
+ return self._module.warn(*args, **kwargs)
+
+ def deprecate(self, *args, **kwargs):
+ return self._module.deprecate(*args, **kwargs)
+
+ def debug(self, *args, **kwargs):
+ return self._module.debug(*args, **kwargs)
+
+ def exit_json(self, *args, **kwargs):
+ return self._module.exit_json(*args, **kwargs)
+
+ def fail_json(self, *args, **kwargs):
+ return self._module.fail_json(*args, **kwargs)
+
+ def fail_from_exception(self, exception):
+ msg = to_text(exception)
+ tb = "".join(
+ traceback.format_exception(None, exception, exception.__traceback__)
+ )
+ return self.fail_json(msg=msg, exception=tb)
+
+ def has_at_least(
+ self, dependency: str, minimum: Optional[str] = None, warn: bool = False
+ ) -> bool:
+ supported = has_at_least(dependency, minimum)
+ if not supported and warn:
+ self.warn(
+ "{0}<{1} is not supported or tested. Some features may not work.".format(
+ dependency, minimum
+ )
+ )
+ return supported
+
+ def requires(
+ self,
+ dependency: str,
+ minimum: Optional[str] = None,
+ reason: Optional[str] = None,
+ ) -> None:
+ try:
+ requires(dependency, minimum, reason=reason)
+ except Exception as e:
+ self.fail_json(msg=to_text(e))
+
+
+def gather_versions() -> dict:
+ versions = {}
+ try:
+ import jsonpatch
+
+ versions["jsonpatch"] = jsonpatch.__version__
+ except ImportError:
+ pass
+
+ try:
+ import kubernetes
+
+ versions["kubernetes"] = kubernetes.__version__
+ except ImportError:
+ pass
+
+ try:
+ import kubernetes_validate
+
+ versions["kubernetes-validate"] = kubernetes_validate.__version__
+ except ImportError:
+ pass
+
+ try:
+ import yaml
+
+ versions["pyyaml"] = yaml.__version__
+ except ImportError:
+ pass
+
+ return versions
+
+
+def has_at_least(dependency: str, minimum: Optional[str] = None) -> bool:
+ """Check if a specific dependency is present at a minimum version.
+
+ If a minimum version is not specified it will check only that the
+ dependency is present.
+ """
+ dependencies = gather_versions()
+ current = dependencies.get(dependency)
+ if current is not None:
+ if minimum is None:
+ return True
+ supported = LooseVersion(current) >= LooseVersion(minimum)
+ return supported
+ return False
+
+
+def requires(
+ dependency: str, minimum: Optional[str] = None, reason: Optional[str] = None
+) -> None:
+ """Fail if a specific dependency is not present at a minimum version.
+
+ If a minimum version is not specified it will require only that the
+ dependency is present. This function raises an exception when the
+ dependency is not found at the required version.
+ """
+ if not has_at_least(dependency, minimum):
+ if minimum is not None:
+ lib = "{0}>={1}".format(dependency, minimum)
+ else:
+ lib = dependency
+ raise Exception(missing_required_lib(lib, reason=reason))
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/exceptions.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/exceptions.py
new file mode 100644
index 00000000..f3a82c6b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/exceptions.py
@@ -0,0 +1,12 @@
+# Copyright: (c) 2021, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+class CoreException(Exception):
+ pass
+
+
+class ResourceTimeout(CoreException):
+ def __init__(self, message="", result=None):
+ self.result = result or {}
+ super().__init__(message)
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/resource.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/resource.py
new file mode 100644
index 00000000..4c9d3e1d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/resource.py
@@ -0,0 +1,134 @@
+# Copyright: (c) 2021, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+import os
+from typing import cast, Dict, Iterable, List, Optional, Union
+
+from ansible.module_utils.six import string_types
+from ansible.module_utils.urls import Request
+
+try:
+ import yaml
+except ImportError:
+ # Handled in module setup
+ pass
+
+
+class ResourceDefinition(dict):
+ """Representation of a resource definition.
+
+ This is a thin wrapper around a dictionary representation of a resource
+ definition, with a few properties defined for conveniently accessing the
+ commonly used fields.
+ """
+
+ @property
+ def kind(self) -> Optional[str]:
+ return self.get("kind")
+
+ @property
+ def api_version(self) -> Optional[str]:
+ return self.get("apiVersion")
+
+ @property
+ def namespace(self) -> Optional[str]:
+ metadata = self.get("metadata", {})
+ return metadata.get("namespace")
+
+ @property
+ def name(self) -> Optional[str]:
+ metadata = self.get("metadata", {})
+ return metadata.get("name")
+
+
+def create_definitions(params: Dict) -> List[ResourceDefinition]:
+ """Create a list of ResourceDefinitions from module inputs.
+
+ This will take the module's inputs and return a list of ResourceDefintion
+ objects. The resource definitions returned by this function should be as
+ complete a definition as we can create based on the input. Any *List kinds
+ will be removed and replaced by the resources contained in it.
+ """
+ if params.get("resource_definition"):
+ d = cast(Union[str, List, Dict], params.get("resource_definition"))
+ definitions = from_yaml(d)
+ elif params.get("src"):
+ d = cast(str, params.get("src"))
+ if hasattr(d, "startswith") and d.startswith(("https://", "http://", "ftp://")):
+ data = Request().open("GET", d).read().decode("utf8")
+ definitions = from_yaml(data)
+ else:
+ definitions = from_file(d)
+ else:
+ # We'll create an empty definition and let merge_params set values
+ # from the module parameters.
+ definitions = [{}]
+
+ resource_definitions: List[Dict] = []
+ for definition in definitions:
+ merge_params(definition, params)
+ kind = cast(Optional[str], definition.get("kind"))
+ if kind and kind.endswith("List"):
+ resource_definitions += flatten_list_kind(definition, params)
+ else:
+ resource_definitions.append(definition)
+ return list(map(ResourceDefinition, resource_definitions))
+
+
+def from_yaml(definition: Union[str, List, Dict]) -> Iterable[Dict]:
+ """Load resource definitions from a yaml definition."""
+ definitions: List[Dict] = []
+ if isinstance(definition, string_types):
+ definitions += yaml.safe_load_all(definition)
+ elif isinstance(definition, list):
+ for item in definition:
+ if isinstance(item, string_types):
+ definitions += yaml.safe_load_all(item)
+ else:
+ definitions.append(item)
+ else:
+ definition = cast(Dict, definition)
+ definitions.append(definition)
+ return filter(None, definitions)
+
+
+def from_file(filepath: str) -> Iterable[Dict]:
+ """Load resource definitions from a path to a yaml file."""
+ path = os.path.normpath(filepath)
+ with open(path, "rb") as f:
+ definitions = list(yaml.safe_load_all(f))
+ return filter(None, definitions)
+
+
+def merge_params(definition: Dict, params: Dict) -> Dict:
+ """Merge module parameters with the resource definition.
+
+ Fields in the resource definition take precedence over module parameters.
+ """
+ definition.setdefault("kind", params.get("kind"))
+ definition.setdefault("apiVersion", params.get("api_version"))
+ metadata = definition.setdefault("metadata", {})
+ # The following should only be set if we have values for them
+ if params.get("namespace"):
+ metadata.setdefault("namespace", params.get("namespace"))
+ if params.get("name"):
+ metadata.setdefault("name", params.get("name"))
+ if params.get("generate_name"):
+ metadata.setdefault("generateName", params.get("generate_name"))
+ return definition
+
+
+def flatten_list_kind(definition: Dict, params: Dict) -> List[Dict]:
+ """Replace *List kind with the items it contains.
+
+ This will take a definition for a *List resource and return a list of
+ definitions for the items contained within the List.
+ """
+ items = []
+ kind = cast(str, definition.get("kind"))[:-4]
+ api_version = definition.get("apiVersion")
+ for item in definition.get("items", []):
+ item.setdefault("kind", kind)
+ item.setdefault("apiVersion", api_version)
+ items.append(merge_params(item, params))
+ return items
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/runner.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/runner.py
new file mode 100644
index 00000000..438b3211
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/runner.py
@@ -0,0 +1,199 @@
+# Copyright: (c) 2021, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from typing import Dict
+
+from ansible.module_utils._text import to_native
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
+ create_definitions,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+ diff_objects,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ ResourceTimeout,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import exists
+from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
+ LabelSelectorFilter,
+)
+
+
+def validate(client, module, resource):
+ def _prepend_resource_info(resource, msg):
+ return "%s %s: %s" % (resource["kind"], resource["metadata"]["name"], msg)
+
+ module.requires("kubernetes-validate")
+
+ warnings, errors = client.validate(
+ resource,
+ module.params["validate"].get("version"),
+ module.params["validate"].get("strict"),
+ )
+
+ if errors and module.params["validate"]["fail_on_error"]:
+ module.fail_json(
+ msg="\n".join([_prepend_resource_info(resource, error) for error in errors])
+ )
+ return [_prepend_resource_info(resource, msg) for msg in warnings + errors]
+
+
+def run_module(module) -> None:
+ results = []
+ changed = False
+ client = get_api_client(module)
+ svc = K8sService(client, module)
+ try:
+ definitions = create_definitions(module.params)
+ except Exception as e:
+ msg = "Failed to load resource definition: {0}".format(e)
+ raise CoreException(msg) from e
+
+ for definition in definitions:
+ result = {"changed": False, "result": {}}
+ warnings = []
+
+ if module.params.get("validate") is not None:
+ warnings = validate(client, module, definition)
+
+ try:
+ result = perform_action(svc, definition, module.params)
+ except Exception as e:
+ try:
+ error = e.result
+ except AttributeError:
+ error = {}
+ try:
+ error["reason"] = e.__cause__.reason
+ except AttributeError:
+ pass
+ error["msg"] = to_native(e)
+ if warnings:
+ error.setdefault("warnings", []).extend(warnings)
+
+ if module.params.get("continue_on_error"):
+ result["error"] = error
+ else:
+ module.fail_json(**error)
+
+ if warnings:
+ result.setdefault("warnings", []).extend(warnings)
+ changed |= result["changed"]
+ results.append(result)
+
+ if len(results) == 1:
+ module.exit_json(**results[0])
+
+ module.exit_json(**{"changed": changed, "result": {"results": results}})
+
+
+def perform_action(svc, definition: Dict, params: Dict) -> Dict:
+ origin_name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+ label_selectors = params.get("label_selectors")
+ state = params.get("state", None)
+ kind = definition.get("kind")
+ api_version = definition.get("apiVersion")
+
+ result = {"changed": False, "result": {}}
+ instance = {}
+
+ resource = svc.find_resource(kind, api_version, fail=True)
+ definition["kind"] = resource.kind
+ definition["apiVersion"] = resource.group_version
+ existing = svc.retrieve(resource, definition)
+
+ if state == "absent":
+ if exists(existing) and existing.kind.endswith("List"):
+ instance = []
+ for item in existing.items:
+ r = svc.delete(resource, item, existing)
+ instance.append(r)
+ else:
+ instance = svc.delete(resource, definition, existing)
+ result["method"] = "delete"
+ if exists(existing):
+ result["changed"] = True
+ else:
+ if label_selectors:
+ filter_selector = LabelSelectorFilter(label_selectors)
+ if not filter_selector.isMatching(definition):
+ result["changed"] = False
+ result["msg"] = (
+ "resource 'kind={kind},name={name},namespace={namespace}' "
+ "filtered by label_selectors.".format(
+ kind=kind,
+ name=origin_name,
+ namespace=namespace,
+ )
+ )
+ return result
+
+ if params.get("apply"):
+ instance = svc.apply(resource, definition, existing)
+ result["method"] = "apply"
+ elif not existing:
+ if state == "patched":
+ result.setdefault("warnings", []).append(
+ "resource 'kind={kind},name={name}' was not found but will not be "
+ "created as 'state' parameter has been set to '{state}'".format(
+ kind=kind, name=definition["metadata"].get("name"), state=state
+ )
+ )
+ return result
+ instance = svc.create(resource, definition)
+ result["method"] = "create"
+ result["changed"] = True
+ elif params.get("force", False):
+ instance = svc.replace(resource, definition, existing)
+ result["method"] = "replace"
+ else:
+ instance = svc.update(resource, definition, existing)
+ result["method"] = "update"
+
+ # If needed, wait and/or create diff
+ success = True
+
+ if result["method"] == "delete":
+ # wait logic is a bit different for delete as `instance` may be a status object
+ if params.get("wait") and not svc.module.check_mode:
+ success, waited, duration = svc.wait(resource, definition)
+ result["duration"] = duration
+ else:
+ if params.get("wait") and not svc.module.check_mode:
+ success, instance, duration = svc.wait(resource, instance)
+ result["duration"] = duration
+
+ if result["method"] not in ("create", "delete"):
+ if existing:
+ existing = existing.to_dict()
+ else:
+ existing = {}
+ match, diffs = diff_objects(existing, instance)
+ if match and diffs:
+ result.setdefault("warnings", []).append(
+ "No meaningful diff was generated, but the API may not be idempotent "
+ "(only metadata.generation or metadata.resourceVersion were changed)"
+ )
+ result["changed"] = not match
+ if svc.module._diff:
+ result["diff"] = diffs
+
+ result["result"] = instance
+ if not success:
+ raise ResourceTimeout(
+ '"{0}" "{1}": Timed out waiting on resource'.format(
+ definition["kind"], origin_name
+ ),
+ result,
+ )
+
+ return result
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/service.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/service.py
new file mode 100644
index 00000000..6a32f9a8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/service.py
@@ -0,0 +1,496 @@
+# Copyright: (c) 2021, Red Hat | Ansible
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from typing import Any, Dict, List, Optional, Tuple
+
+from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
+ generate_hash,
+)
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
+ Waiter,
+ exists,
+ resource_absent,
+ get_waiter,
+)
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ requires,
+)
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+from ansible.module_utils.common.dict_transformations import dict_merge
+
+try:
+ from kubernetes.dynamic.exceptions import (
+ NotFoundError,
+ ResourceNotFoundError,
+ ResourceNotUniqueError,
+ ConflictError,
+ ForbiddenError,
+ MethodNotAllowedError,
+ BadRequestError,
+ )
+except ImportError:
+ # Handled in module setup
+ pass
+
+try:
+ from kubernetes.dynamic.resource import Resource, ResourceInstance
+except ImportError:
+ # These are defined only for the sake of Ansible's checked import requirement
+ Resource = Any # type: ignore
+ ResourceInstance = Any # type: ignore
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
+ apply_object,
+ )
+except ImportError:
+ # Handled in module setup
+ pass
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
+ recursive_diff,
+ )
+except ImportError:
+ from ansible.module_utils.common.dict_transformations import recursive_diff
+
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.common import (
+ _encode_stringdata,
+ )
+except ImportError:
+ # Handled in module setup
+ pass
+
+
+class K8sService:
+ """A Service class for K8S modules.
+ This class has the primary purpose is to perform work on the cluster (e.g., create, apply, replace, update, delete).
+ """
+
+ def __init__(self, client, module) -> None:
+ self.client = client
+ self.module = module
+
+ @property
+ def _client_side_dry_run(self):
+ return self.module.check_mode and not self.client.dry_run
+
+ def find_resource(
+ self, kind: str, api_version: str, fail: bool = False
+ ) -> Optional[Resource]:
+ try:
+ return self.client.resource(kind, api_version)
+ except (ResourceNotFoundError, ResourceNotUniqueError):
+ if fail:
+ raise CoreException(
+ "Failed to find exact match for %s.%s by [kind, name, singularName, shortNames]"
+ % (api_version, kind)
+ )
+
+ def wait(
+ self, resource: Resource, instance: Dict
+ ) -> Tuple[bool, Optional[Dict], int]:
+ wait_sleep = self.module.params.get("wait_sleep")
+ wait_timeout = self.module.params.get("wait_timeout")
+ wait_condition = None
+ if self.module.params.get("wait_condition") and self.module.params[
+ "wait_condition"
+ ].get("type"):
+ wait_condition = self.module.params["wait_condition"]
+ state = "present"
+ if self.module.params.get("state") == "absent":
+ state = "absent"
+ label_selectors = self.module.params.get("label_selectors")
+
+ waiter = get_waiter(
+ self.client, resource, condition=wait_condition, state=state
+ )
+ return waiter.wait(
+ timeout=wait_timeout,
+ sleep=wait_sleep,
+ name=instance["metadata"].get("name"),
+ namespace=instance["metadata"].get("namespace"),
+ label_selectors=label_selectors,
+ )
+
+ def create_project_request(self, definition: Dict) -> Dict:
+ definition["kind"] = "ProjectRequest"
+ results = {"changed": False, "result": {}}
+ resource = self.find_resource(
+ "ProjectRequest", definition["apiVersion"], fail=True
+ )
+ if not self.module.check_mode:
+ try:
+ k8s_obj = self.client.create(resource, definition)
+ results["result"] = k8s_obj.to_dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to create object: {0}".format(reason)
+ raise CoreException(msg) from e
+
+ results["changed"] = True
+
+ return results
+
+ def patch_resource(
+ self,
+ resource: Resource,
+ definition: Dict,
+ name: str,
+ namespace: str,
+ merge_type: str = None,
+ ) -> Dict:
+ if merge_type == "json":
+ self.module.deprecate(
+ msg="json as a merge_type value is deprecated. Please use the k8s_json_patch module instead.",
+ version="3.0.0",
+ collection_name="kubernetes.core",
+ )
+ try:
+ params = dict(name=name, namespace=namespace)
+ if merge_type:
+ params["content_type"] = "application/{0}-patch+json".format(merge_type)
+ return self.client.patch(resource, definition, **params).to_dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to patch object: {0}".format(reason)
+ raise CoreException(msg) from e
+
+ def retrieve(self, resource: Resource, definition: Dict) -> ResourceInstance:
+ state = self.module.params.get("state", None)
+ append_hash = self.module.params.get("append_hash", False)
+ name = definition["metadata"].get("name")
+ generate_name = definition["metadata"].get("generateName")
+ namespace = definition["metadata"].get("namespace")
+ label_selectors = self.module.params.get("label_selectors")
+ existing: ResourceInstance = None
+
+ try:
+ # ignore append_hash for resources other than ConfigMap and Secret
+ if append_hash and definition["kind"] in ["ConfigMap", "Secret"]:
+ if name:
+ name = "%s-%s" % (name, generate_hash(definition))
+ definition["metadata"]["name"] = name
+ elif generate_name:
+ definition["metadata"]["generateName"] = "%s-%s" % (
+ generate_name,
+ generate_hash(definition),
+ )
+ params = {}
+ if name:
+ params["name"] = name
+ if namespace:
+ params["namespace"] = namespace
+ if label_selectors:
+ params["label_selector"] = ",".join(label_selectors)
+ if "name" in params or "label_selector" in params:
+ existing = self.client.get(resource, **params)
+ except (NotFoundError, MethodNotAllowedError):
+ pass
+ except ForbiddenError as e:
+ if (
+ definition["kind"] in ["Project", "ProjectRequest"]
+ and state != "absent"
+ ):
+ return self.create_project_request(definition)
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to retrieve requested object: {0}".format(reason)
+ raise CoreException(msg) from e
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to retrieve requested object: {0}".format(reason)
+ raise CoreException(msg) from e
+
+ return existing
+
+ def find(
+ self,
+ kind: str,
+ api_version: str,
+ name: str = None,
+ namespace: Optional[str] = None,
+ label_selectors: Optional[List[str]] = None,
+ field_selectors: Optional[List[str]] = None,
+ wait: Optional[bool] = False,
+ wait_sleep: Optional[int] = 5,
+ wait_timeout: Optional[int] = 120,
+ state: Optional[str] = "present",
+ condition: Optional[Dict] = None,
+ ) -> Dict:
+ resource = self.find_resource(kind, api_version)
+ api_found = bool(resource)
+ if not api_found:
+ return dict(
+ resources=[],
+ msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format(
+ api_version, kind
+ ),
+ api_found=False,
+ )
+
+ if not label_selectors:
+ label_selectors = []
+ if not field_selectors:
+ field_selectors = []
+
+ result = {"resources": [], "api_found": True}
+
+ # With a timeout of 0 the waiter will do a single check and return, effectively not waiting.
+ if not wait:
+ wait_timeout = 0
+
+ if state == "present":
+ predicate = exists
+ else:
+ predicate = resource_absent
+
+ waiter = Waiter(self.client, resource, predicate)
+
+ # This is an initial check to get the resource or resources that we then need to wait on individually.
+ try:
+ success, resources, duration = waiter.wait(
+ timeout=wait_timeout,
+ sleep=wait_sleep,
+ name=name,
+ namespace=namespace,
+ label_selectors=label_selectors,
+ field_selectors=field_selectors,
+ )
+ except BadRequestError:
+ return result
+ except CoreException as e:
+ raise e
+ except Exception as e:
+ raise CoreException(
+ "Exception '{0}' raised while trying to get resource using (name={1}, namespace={2}, label_selectors={3}, field_selectors={4})".format(
+ e, name, namespace, label_selectors, field_selectors
+ )
+ )
+
+ # There is either no result or there is a List resource with no items
+ if (
+ not resources
+ or resources["kind"].endswith("List")
+ and not resources.get("items")
+ ):
+ return result
+
+ instances = resources.get("items") or [resources]
+
+ if not wait:
+ result["resources"] = instances
+ return result
+
+ # Now wait for the specified state of any resource instances we have found.
+ waiter = get_waiter(self.client, resource, state=state, condition=condition)
+ for instance in instances:
+ name = instance["metadata"].get("name")
+ namespace = instance["metadata"].get("namespace")
+ success, res, duration = waiter.wait(
+ timeout=wait_timeout,
+ sleep=wait_sleep,
+ name=name,
+ namespace=namespace,
+ )
+ if not success:
+ raise CoreException(
+ "Failed to gather information about %s(s) even"
+ " after waiting for %s seconds" % (res.get("kind"), duration)
+ )
+ result["resources"].append(res)
+ return result
+
+ def create(self, resource: Resource, definition: Dict) -> Dict:
+ namespace = definition["metadata"].get("namespace")
+ name = definition["metadata"].get("name")
+
+ if self._client_side_dry_run:
+ k8s_obj = _encode_stringdata(definition)
+ else:
+ try:
+ k8s_obj = self.client.create(
+ resource, definition, namespace=namespace
+ ).to_dict()
+ except ConflictError:
+ # Some resources, like ProjectRequests, can't be created multiple times,
+ # because the resources that they create don't match their kind
+ # In this case we'll mark it as unchanged and warn the user
+ self.module.warn(
+ "{0} was not found, but creating it returned a 409 Conflict error. This can happen \
+ if the resource you are creating does not directly create a resource of the same kind.".format(
+ name
+ )
+ )
+ return dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to create object: {0}".format(reason)
+ raise CoreException(msg) from e
+ return k8s_obj
+
+ def apply(
+ self,
+ resource: Resource,
+ definition: Dict,
+ existing: Optional[ResourceInstance] = None,
+ ) -> Dict:
+ namespace = definition["metadata"].get("namespace")
+
+ server_side_apply = self.module.params.get("server_side_apply")
+ if server_side_apply:
+ requires("kubernetes", "19.15.0", reason="to use server side apply")
+ if self._client_side_dry_run:
+ ignored, patch = apply_object(resource, _encode_stringdata(definition))
+ if existing:
+ k8s_obj = dict_merge(existing.to_dict(), patch)
+ else:
+ k8s_obj = patch
+ else:
+ try:
+ params = {}
+ if server_side_apply:
+ params["server_side"] = True
+ params.update(server_side_apply)
+ k8s_obj = self.client.apply(
+ resource, definition, namespace=namespace, **params
+ ).to_dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to apply object: {0}".format(reason)
+ raise CoreException(msg) from e
+ return k8s_obj
+
+ def replace(
+ self,
+ resource: Resource,
+ definition: Dict,
+ existing: ResourceInstance,
+ ) -> Dict:
+ append_hash = self.module.params.get("append_hash", False)
+ name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+
+ if self._client_side_dry_run:
+ k8s_obj = _encode_stringdata(definition)
+ else:
+ try:
+ k8s_obj = self.client.replace(
+ resource,
+ definition,
+ name=name,
+ namespace=namespace,
+ append_hash=append_hash,
+ ).to_dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to replace object: {0}".format(reason)
+ raise CoreException(msg) from e
+ return k8s_obj
+
+ def update(
+ self, resource: Resource, definition: Dict, existing: ResourceInstance
+ ) -> Dict:
+ name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+
+ if self._client_side_dry_run:
+ k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition))
+ else:
+ exception = None
+ for merge_type in self.module.params.get("merge_type") or [
+ "strategic-merge",
+ "merge",
+ ]:
+ try:
+ k8s_obj = self.patch_resource(
+ resource,
+ definition,
+ name,
+ namespace,
+ merge_type=merge_type,
+ )
+ exception = None
+ except CoreException as e:
+ exception = e
+ continue
+ break
+ if exception:
+ raise exception
+ return k8s_obj
+
+ def delete(
+ self,
+ resource: Resource,
+ definition: Dict,
+ existing: Optional[ResourceInstance] = None,
+ ) -> Dict:
+ delete_options = self.module.params.get("delete_options")
+ label_selectors = self.module.params.get("label_selectors")
+ name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+ params = {}
+
+ if not exists(existing):
+ return {}
+
+ # Delete the object
+ if self._client_side_dry_run:
+ return {}
+
+ if name:
+ params["name"] = name
+
+ if namespace:
+ params["namespace"] = namespace
+
+ if label_selectors:
+ params["label_selector"] = ",".join(label_selectors)
+
+ if delete_options:
+ body = {
+ "apiVersion": "v1",
+ "kind": "DeleteOptions",
+ }
+ body.update(delete_options)
+ params["body"] = body
+
+ try:
+ k8s_obj = self.client.delete(resource, **params).to_dict()
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to delete object: {0}".format(reason)
+ raise CoreException(msg) from e
+ return k8s_obj
+
+
+def diff_objects(existing: Dict, new: Dict) -> Tuple[bool, Dict]:
+ result = {}
+ diff = recursive_diff(existing, new)
+ if not diff:
+ return True, result
+
+ result["before"] = diff[0]
+ result["after"] = diff[1]
+
+ if list(result["after"].keys()) != ["metadata"] or list(
+ result["before"].keys()
+ ) != ["metadata"]:
+ return False, result
+
+ # If only metadata.generation and metadata.resourceVersion changed, ignore it
+ ignored_keys = set(["generation", "resourceVersion"])
+
+ if not set(result["after"]["metadata"].keys()).issubset(ignored_keys):
+ return False, result
+ if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
+ return False, result
+
+ return True, result
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8s/waiter.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/waiter.py
new file mode 100644
index 00000000..653e1708
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8s/waiter.py
@@ -0,0 +1,238 @@
+import time
+from functools import partial
+from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
+
+from ansible.module_utils.parsing.convert_bool import boolean
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+try:
+ from kubernetes.dynamic.exceptions import NotFoundError
+ from kubernetes.dynamic.resource import Resource, ResourceField, ResourceInstance
+except ImportError:
+ # These are defined only for the sake of Ansible's checked import requirement
+ Resource = Any # type: ignore
+ ResourceInstance = Any # type: ignore
+ pass
+
+try:
+ from urllib3.exceptions import HTTPError
+except ImportError:
+ # Handled during module setup
+ pass
+
+
+def deployment_ready(deployment: ResourceInstance) -> bool:
+ # FIXME: frustratingly bool(deployment.status) is True even if status is empty
+ # Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
+ # deployment.status.replicas is None is perfectly ok if desired replicas == 0
+ # Scaling up means that we also need to check that we're not in a
+ # situation where status.replicas == status.availableReplicas
+ # but spec.replicas != status.replicas
+ return bool(
+ deployment.status
+ and deployment.spec.replicas == (deployment.status.replicas or 0)
+ and deployment.status.availableReplicas == deployment.status.replicas
+ and deployment.status.observedGeneration == deployment.metadata.generation
+ and not deployment.status.unavailableReplicas
+ )
+
+
+def pod_ready(pod: ResourceInstance) -> bool:
+ return bool(
+ pod.status
+ and pod.status.containerStatuses is not None
+ and all(container.ready for container in pod.status.containerStatuses)
+ )
+
+
+def daemonset_ready(daemonset: ResourceInstance) -> bool:
+ return bool(
+ daemonset.status
+ and daemonset.status.desiredNumberScheduled is not None
+ and daemonset.status.updatedNumberScheduled
+ == daemonset.status.desiredNumberScheduled
+ and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled
+ and daemonset.status.observedGeneration == daemonset.metadata.generation
+ and not daemonset.status.unavailableReplicas
+ )
+
+
+def statefulset_ready(statefulset: ResourceInstance) -> bool:
+ # These may be None
+ updated_replicas = statefulset.status.updatedReplicas or 0
+ ready_replicas = statefulset.status.readyReplicas or 0
+ return bool(
+ statefulset.status
+ and statefulset.spec.updateStrategy.type == "RollingUpdate"
+ and statefulset.status.observedGeneration
+ == (statefulset.metadata.generation or 0)
+ and statefulset.status.updateRevision == statefulset.status.currentRevision
+ and updated_replicas == statefulset.spec.replicas
+ and ready_replicas == statefulset.spec.replicas
+ and statefulset.status.replicas == statefulset.spec.replicas
+ )
+
+
+def custom_condition(condition: Dict, resource: ResourceInstance) -> bool:
+ if not resource.status or not resource.status.conditions:
+ return False
+ matches = [x for x in resource.status.conditions if x.type == condition["type"]]
+ if not matches:
+ return False
+ # There should never be more than one condition of a specific type
+ match: ResourceField = matches[0]
+ if match.status == "Unknown":
+ if match.status == condition["status"]:
+ if "reason" not in condition:
+ return True
+ if condition["reason"]:
+ return match.reason == condition["reason"]
+ return False
+ status = True if match.status == "True" else False
+ if status == boolean(condition["status"], strict=False):
+ if condition.get("reason"):
+ return match.reason == condition["reason"]
+ return True
+ return False
+
+
+def resource_absent(resource: ResourceInstance) -> bool:
+ return not exists(resource)
+
+
+def exists(resource: Optional[ResourceInstance]) -> bool:
+ """Simple predicate to check for existence of a resource.
+
+ While a List type resource technically always exists, this will only return
+ true if the List contains items."""
+ return bool(resource) and not empty_list(resource)
+
+
+RESOURCE_PREDICATES = {
+ "DaemonSet": daemonset_ready,
+ "Deployment": deployment_ready,
+ "Pod": pod_ready,
+ "StatefulSet": statefulset_ready,
+}
+
+
+def empty_list(resource: ResourceInstance) -> bool:
+ return resource["kind"].endswith("List") and not resource.get("items")
+
+
+def clock(total: int, interval: int) -> Iterator[int]:
+ start = time.monotonic()
+ yield 0
+ while (time.monotonic() - start) < total:
+ time.sleep(interval)
+ yield int(time.monotonic() - start)
+
+
+class Waiter:
+ def __init__(
+ self, client, resource: Resource, predicate: Callable[[ResourceInstance], bool]
+ ):
+ self.client = client
+ self.resource = resource
+ self.predicate = predicate
+
+ def wait(
+ self,
+ timeout: int,
+ sleep: int,
+ name: Optional[str] = None,
+ namespace: Optional[str] = None,
+ label_selectors: Optional[List[str]] = None,
+ field_selectors: Optional[List[str]] = None,
+ ) -> Tuple[bool, Dict, int]:
+ params = {}
+
+ if name:
+ params["name"] = name
+
+ if namespace:
+ params["namespace"] = namespace
+
+ if label_selectors:
+ params["label_selector"] = ",".join(label_selectors)
+
+ if field_selectors:
+ params["field_selector"] = ",".join(field_selectors)
+
+ instance = {}
+ response = None
+ elapsed = 0
+ for i in clock(timeout, sleep):
+ exception = None
+ elapsed = i
+ try:
+ response = self.client.get(self.resource, **params)
+ except NotFoundError:
+ response = None
+ # Retry connection errors as it may be intermittent network issues
+ except HTTPError as e:
+ exception = e
+ if self.predicate(response):
+ break
+ if exception:
+ msg = (
+ "Exception '{0}' raised while trying to get resource using {1}".format(
+ exception, params
+ )
+ )
+ raise CoreException(msg) from exception
+ if response:
+ instance = response.to_dict()
+ return self.predicate(response), instance, elapsed
+
+
+class DummyWaiter:
+ """A no-op waiter that simply returns the item being waited on.
+
+ No API call will be made with this waiter; the function returns
+ immediately. This waiter is useful for waiting on resource instances in
+ check mode, for example.
+ """
+
+ def wait(
+ self,
+ definition: Dict,
+ timeout: int,
+ sleep: int,
+ label_selectors: Optional[List[str]] = None,
+ ) -> Tuple[bool, Optional[Dict], int]:
+ return True, definition, 0
+
+
+# The better solution would be typing.Protocol, but this is only in 3.8+
+SupportsWait = Union[Waiter, DummyWaiter]
+
+
+def get_waiter(
+ client,
+ resource: Resource,
+ state: str = "present",
+ condition: Optional[Dict] = None,
+ check_mode: Optional[bool] = False,
+) -> SupportsWait:
+ """Create a Waiter object based on the specified resource.
+
+ This is a convenience method for creating a waiter from a resource.
+ Based on the arguments and the kind of resource, an appropriate waiter
+ will be returned. A waiter can also be created directly, of course.
+ """
+ if check_mode:
+ return DummyWaiter()
+ if state == "present":
+ if condition:
+ predicate: Callable[[ResourceInstance], bool] = partial(
+ custom_condition, condition
+ )
+ else:
+ predicate = RESOURCE_PREDICATES.get(resource.kind, exists)
+ else:
+ predicate = resource_absent
+ return Waiter(client, resource, predicate)
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/k8sdynamicclient.py b/ansible_collections/kubernetes/core/plugins/module_utils/k8sdynamicclient.py
new file mode 100644
index 00000000..b1beca4c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/k8sdynamicclient.py
@@ -0,0 +1,50 @@
+# Copyright [2017] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+from kubernetes.dynamic import DynamicClient
+
+from ansible_collections.kubernetes.core.plugins.module_utils.apply import k8s_apply
+from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
+ ApplyException,
+)
+
+
+class K8SDynamicClient(DynamicClient):
+ def apply(self, resource, body=None, name=None, namespace=None, **kwargs):
+ body = super().serialize_body(body)
+ body["metadata"] = body.get("metadata", dict())
+ name = name or body["metadata"].get("name")
+ if not name:
+ raise ValueError(
+ "name is required to apply {0}.{1}".format(
+ resource.group_version, resource.kind
+ )
+ )
+ if resource.namespaced:
+ body["metadata"]["namespace"] = super().ensure_namespace(
+ resource, namespace, body
+ )
+ try:
+ return k8s_apply(resource, body, **kwargs)
+ except ApplyException as e:
+ raise ValueError(
+ "Could not apply strategic merge to %s/%s: %s"
+ % (body["kind"], body["metadata"]["name"], e)
+ )
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/selector.py b/ansible_collections/kubernetes/core/plugins/module_utils/selector.py
new file mode 100644
index 00000000..2a85d0bf
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/selector.py
@@ -0,0 +1,79 @@
+# Copyright [2021] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+
+
+class Selector(object):
+
+ equality_based_operators = ("==", "!=", "=")
+
+ def __init__(self, data):
+ self._operator = None
+ self._data = None
+ if not self.parse_set_based_requirement(data):
+ no_whitespace_data = data.replace(" ", "")
+ for op in self.equality_based_operators:
+ idx = no_whitespace_data.find(op)
+ if idx != -1:
+ self._operator = "in" if op == "==" or op == "=" else "notin"
+ self._key = no_whitespace_data[0:idx]
+ # fmt: off
+ self._data = [no_whitespace_data[idx + len(op):]]
+ # fmt: on
+ break
+
+ def parse_set_based_requirement(self, data):
+ m = re.match(
+ r"( *)([a-z0-9A-Z][a-z0-9A-Z\._-]*[a-z0-9A-Z])( +)(notin|in)( +)\((.*)\)( *)",
+ data,
+ )
+ if m:
+ self._set_based_requirement = True
+ self._key = m.group(2)
+ self._operator = m.group(4)
+ self._data = [x.replace(" ", "") for x in m.group(6).split(",") if x != ""]
+ return True
+ elif all(x not in data for x in self.equality_based_operators):
+ self._key = data.rstrip(" ").lstrip(" ")
+ if self._key.startswith("!"):
+ self._key = self._key[1:].lstrip(" ")
+ self._operator = "!"
+ return True
+ return False
+
+ def isMatch(self, labels):
+ if self._operator == "in":
+ return self._key in labels and labels.get(self._key) in self._data
+ elif self._operator == "notin":
+ return self._key not in labels or labels.get(self._key) not in self._data
+ else:
+ return (
+ self._key not in labels
+ if self._operator == "!"
+ else self._key in labels
+ )
+
+
+class LabelSelectorFilter(object):
+ def __init__(self, label_selectors):
+ self.selectors = [Selector(data) for data in label_selectors]
+
+ def isMatching(self, definition):
+ if "metadata" not in definition or "labels" not in definition["metadata"]:
+ return False
+ labels = definition["metadata"]["labels"]
+ if not isinstance(labels, dict):
+ return None
+ return all(sel.isMatch(labels) for sel in self.selectors)
diff --git a/ansible_collections/kubernetes/core/plugins/module_utils/version.py b/ansible_collections/kubernetes/core/plugins/module_utils/version.py
new file mode 100644
index 00000000..78dc59b4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/module_utils/version.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""Provide version object to compare version numbers."""
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
+# remove the _version.py file, and replace the following import by
+#
+# from ansible.module_utils.compat.version import LooseVersion
+
+from ._version import LooseVersion # noqa: F401
diff --git a/ansible_collections/kubernetes/core/plugins/modules/__init__.py b/ansible_collections/kubernetes/core/plugins/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/__init__.py
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm.py b/ansible_collections/kubernetes/core/plugins/modules/helm.py
new file mode 100644
index 00000000..9b2ed386
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm.py
@@ -0,0 +1,924 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm
+
+short_description: Manages Kubernetes packages with the Helm package manager
+
+version_added: "0.11.0"
+
+author:
+ - Lucas Boisserie (@LucasBoisserie)
+ - Matthieu Diehr (@d-matt)
+
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+ - "yaml (https://pypi.org/project/PyYAML/)"
+
+description:
+ - Install, upgrade, delete packages with the Helm package manager.
+
+notes:
+ - The default idempotency check can fail to report changes when C(release_state) is set to C(present)
+ and C(chart_repo_url) is defined. Install helm diff >= 3.4.1 for better results.
+
+options:
+ chart_ref:
+ description:
+ - chart_reference on chart repository.
+ - path to a packaged chart.
+ - path to an unpacked chart directory.
+ - absolute URL.
+ - Required when I(release_state) is set to C(present).
+ required: false
+ type: path
+ chart_repo_url:
+ description:
+ - Chart repository URL where to locate the requested chart.
+ required: false
+ type: str
+ chart_version:
+ description:
+ - Chart version to install. If this is not specified, the latest version is installed.
+ required: false
+ type: str
+ dependency_update:
+ description:
+ - Run standalone C(helm dependency update CHART) before the operation.
+ - Run inline C(--dependency-update) with C(helm install) command. This feature is not supported yet with the C(helm upgrade) command.
+ - So we should consider to use I(dependency_update) options with I(replace) option enabled when specifying I(chart_repo_url).
+ - The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
+ - For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
+ default: false
+ type: bool
+ aliases: [ dep_up ]
+ version_added: "2.4.0"
+ release_name:
+ description:
+ - Release name to manage.
+ required: true
+ type: str
+ aliases: [ name ]
+ release_namespace:
+ description:
+ - Kubernetes namespace where the chart should be installed.
+ required: true
+ type: str
+ aliases: [ namespace ]
+ release_state:
+ choices: ['present', 'absent']
+ description:
+ - Desirated state of release.
+ required: false
+ default: present
+ aliases: [ state ]
+ type: str
+ release_values:
+ description:
+ - Value to pass to chart.
+ required: false
+ default: {}
+ aliases: [ values ]
+ type: dict
+ values_files:
+ description:
+ - Value files to pass to chart.
+ - Paths will be read from the target host's filesystem, not the host running ansible.
+ - values_files option is evaluated before values option if both are used.
+ - Paths are evaluated in the order the paths are specified.
+ required: false
+ default: []
+ type: list
+ elements: str
+ version_added: '1.1.0'
+ update_repo_cache:
+ description:
+ - Run C(helm repo update) before the operation. Can be run as part of the package installation or as a separate step (see Examples).
+ default: false
+ type: bool
+ set_values:
+ description:
+ - Values to pass to chart configuration
+ required: false
+ type: list
+ elements: dict
+ suboptions:
+ value:
+ description:
+ - Value to pass to chart configuration (e.g phase=prod).
+ type: str
+ required: true
+ value_type:
+ description:
+ - Use C(raw) set individual value.
+ - Use C(string) to force a string for an individual value.
+ - Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
+ - Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
+ default: raw
+ choices:
+ - raw
+ - string
+ - json
+ - file
+ version_added: '2.4.0'
+
+#Helm options
+ disable_hook:
+ description:
+ - Helm option to disable hook on install/upgrade/delete.
+ default: False
+ type: bool
+ force:
+ description:
+ - Helm option to force reinstall, ignore on new install.
+ default: False
+ type: bool
+ purge:
+ description:
+ - Remove the release from the store and make its name free for later use.
+ default: True
+ type: bool
+ wait:
+ description:
+ - When I(release_state) is set to C(present), wait until all Pods, PVCs, Services,
+ and minimum number of Pods of a Deployment are in a ready state before marking the release as successful.
+ - When I(release_state) is set to C(absent), will wait until all the resources are deleted before returning.
+ It will wait for as long as I(wait_timeout). This feature requires helm>=3.7.0. Added in version 2.3.0.
+ default: False
+ type: bool
+ wait_timeout:
+ description:
+ - Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).
+ - The use of I(wait_timeout) to wait for kubernetes commands to complete has been deprecated and will be removed after 2022-12-01.
+ type: str
+ timeout:
+ description:
+ - A Go duration (described here I(https://pkg.go.dev/time#ParseDuration)) value to wait for Kubernetes commands to complete. This defaults to 5m0s.
+ - similar to C(wait_timeout) but does not required C(wait) to be activated.
+ - Mutually exclusive with C(wait_timeout).
+ type: str
+ version_added: "2.3.0"
+ atomic:
+ description:
+ - If set, the installation process deletes the installation on failure.
+ type: bool
+ default: False
+ create_namespace:
+ description:
+ - Create the release namespace if not present.
+ type: bool
+ default: False
+ version_added: "0.11.1"
+ post_renderer:
+ description:
+ - Path to an executable to be used for post rendering.
+ type: str
+ version_added: "2.4.0"
+ replace:
+ description:
+ - Reuse the given name, only if that name is a deleted release which remains in the history.
+ - This is unsafe in production environment.
+ - mutually exclusive with with C(history_max).
+ type: bool
+ default: False
+ version_added: "1.11.0"
+ skip_crds:
+ description:
+ - Skip custom resource definitions when installing or upgrading.
+ type: bool
+ default: False
+ version_added: "1.2.0"
+ history_max:
+ description:
+ - Limit the maximum number of revisions saved per release.
+ - mutually exclusive with with C(replace).
+ type: int
+ version_added: "2.2.0"
+extends_documentation_fragment:
+ - kubernetes.core.helm_common_options
+"""
+
+EXAMPLES = r"""
+- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/prometheus
+ release_namespace: monitoring
+ create_namespace: true
+
+# From repository
+- name: Add stable chart repo
+ kubernetes.core.helm_repository:
+ name: stable
+ repo_url: "https://kubernetes.github.io/ingress-nginx"
+
+- name: Deploy latest version of Grafana chart inside monitoring namespace with values
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ release_namespace: monitoring
+ values:
+ replicas: 2
+
+- name: Deploy Grafana chart on 5.0.12 with values loaded from template
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ chart_version: 5.0.12
+ values: "{{ lookup('template', 'somefile.yaml') | from_yaml }}"
+
+- name: Deploy Grafana chart using values files on target
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ release_namespace: monitoring
+ values_files:
+ - /path/to/values.yaml
+
+- name: Remove test release and waiting suppression ending
+ kubernetes.core.helm:
+ name: test
+ state: absent
+ wait: true
+
+- name: Separately update the repository cache
+ kubernetes.core.helm:
+ name: dummy
+ namespace: kube-system
+ state: absent
+ update_repo_cache: true
+
+- name: Deploy Grafana chart using set values on target
+ kubernetes.core.helm:
+ name: test
+ chart_ref: stable/grafana
+ release_namespace: monitoring
+ set_values:
+ - value: phase=prod
+ value_type: string
+
+# From git
+- name: Git clone stable repo on HEAD
+ ansible.builtin.git:
+ repo: "http://github.com/helm/charts.git"
+ dest: /tmp/helm_repo
+
+- name: Deploy Grafana chart from local path
+ kubernetes.core.helm:
+ name: test
+ chart_ref: /tmp/helm_repo/stable/grafana
+ release_namespace: monitoring
+
+# From url
+- name: Deploy Grafana chart on 5.6.0 from url
+ kubernetes.core.helm:
+ name: test
+ chart_ref: "https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz"
+ release_namespace: monitoring
+
+# Using complex Values
+- name: Deploy new-relic client chart
+ kubernetes.core.helm:
+ name: newrelic-bundle
+ chart_ref: newrelic/nri-bundle
+ release_namespace: default
+ force: True
+ wait: True
+ replace: True
+ update_repo_cache: True
+ disable_hook: True
+ values:
+ global:
+ licenseKey: "{{ nr_license_key }}"
+ cluster: "{{ site_name }}"
+ newrelic-infrastructure:
+ privileged: True
+ ksm:
+ enabled: True
+ prometheus:
+ enabled: True
+ kubeEvents:
+ enabled: True
+ logging:
+ enabled: True
+"""
+
+RETURN = r"""
+status:
+ type: complex
+ description: A dictionary of status output
+ returned: on success Creation/Upgrade/Already deploy
+ contains:
+ appversion:
+ type: str
+ returned: always
+ description: Version of app deployed
+ chart:
+ type: str
+ returned: always
+ description: Chart name and chart version
+ name:
+ type: str
+ returned: always
+ description: Name of the release
+ namespace:
+ type: str
+ returned: always
+ description: Namespace where the release is deployed
+ revision:
+ type: str
+ returned: always
+ description: Number of time where the release has been updated
+ status:
+ type: str
+ returned: always
+ description: Status of release (can be DEPLOYED, FAILED, ...)
+ updated:
+ type: str
+ returned: always
+ description: The Date of last update
+ values:
+ type: str
+ returned: always
+ description: Dict of Values used to deploy
+stdout:
+ type: str
+ description: Full `helm` command stdout, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+stderr:
+ type: str
+ description: Full `helm` command stderr, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+command:
+ type: str
+ description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: helm upgrade ...
+"""
+
+import re
+import tempfile
+import traceback
+import copy
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+try:
+ import yaml
+
+ IMP_YAML = True
+ IMP_YAML_ERR = None
+except ImportError:
+ IMP_YAML_ERR = traceback.format_exc()
+ IMP_YAML = False
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+ parse_helm_plugin_list,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
+ HELM_AUTH_ARG_SPEC,
+)
+
+
+def get_release(state, release_name):
+ """
+ Get Release from all deployed releases
+ """
+
+ if state is not None:
+ for release in state:
+ if release["name"] == release_name:
+ return release
+ return None
+
+
+def get_release_status(module, release_name):
+ """
+ Get Release state from deployed release
+ """
+
+ list_command = (
+ module.get_helm_binary() + " list --output=yaml --filter " + release_name
+ )
+
+ rc, out, err = module.run_helm_command(list_command)
+
+ release = get_release(yaml.safe_load(out), release_name)
+
+ if release is None: # not install
+ return None
+
+ release["values"] = module.get_values(release_name)
+
+ return release
+
+
+def run_repo_update(module):
+ """
+ Run Repo update
+ """
+ repo_update_command = module.get_helm_binary() + " repo update"
+ rc, out, err = module.run_helm_command(repo_update_command)
+
+
+def run_dep_update(module, chart_ref):
+ """
+ Run dependency update
+ """
+ dep_update = module.get_helm_binary() + " dependency update " + chart_ref
+ rc, out, err = module.run_helm_command(dep_update)
+
+
+def fetch_chart_info(module, command, chart_ref):
+ """
+ Get chart info
+ """
+ inspect_command = command + " show chart " + chart_ref
+
+ rc, out, err = module.run_helm_command(inspect_command)
+
+ return yaml.safe_load(out)
+
+
+def deploy(
+ command,
+ release_name,
+ release_values,
+ chart_name,
+ wait,
+ wait_timeout,
+ disable_hook,
+ force,
+ values_files,
+ history_max,
+ atomic=False,
+ create_namespace=False,
+ replace=False,
+ post_renderer=None,
+ skip_crds=False,
+ timeout=None,
+ dependency_update=None,
+ set_value_args=None,
+):
+ """
+ Install/upgrade/rollback release chart
+ """
+ if replace:
+ # '--replace' is not supported by 'upgrade -i'
+ deploy_command = command + " install"
+ if dependency_update:
+ deploy_command += " --dependency-update"
+ else:
+ deploy_command = command + " upgrade -i" # install/upgrade
+
+ # Always reset values to keep release_values equal to values released
+ deploy_command += " --reset-values"
+
+ if wait:
+ deploy_command += " --wait"
+ if wait_timeout is not None:
+ deploy_command += " --timeout " + wait_timeout
+
+ if atomic:
+ deploy_command += " --atomic"
+
+ if timeout:
+ deploy_command += " --timeout " + timeout
+
+ if force:
+ deploy_command += " --force"
+
+ if replace:
+ deploy_command += " --replace"
+
+ if disable_hook:
+ deploy_command += " --no-hooks"
+
+ if create_namespace:
+ deploy_command += " --create-namespace"
+
+ if values_files:
+ for value_file in values_files:
+ deploy_command += " --values=" + value_file
+
+ if release_values != {}:
+ fd, path = tempfile.mkstemp(suffix=".yml")
+ with open(path, "w") as yaml_file:
+ yaml.dump(release_values, yaml_file, default_flow_style=False)
+ deploy_command += " -f=" + path
+
+ if post_renderer:
+ deploy_command = " --post-renderer=" + post_renderer
+
+ if skip_crds:
+ deploy_command += " --skip-crds"
+
+ if history_max is not None:
+ deploy_command += " --history-max=%s" % str(history_max)
+
+ if set_value_args:
+ deploy_command += " " + set_value_args
+
+ deploy_command += " " + release_name + " " + chart_name
+ return deploy_command
+
+
+def delete(command, release_name, purge, disable_hook, wait, wait_timeout):
+ """
+ Delete release chart
+ """
+
+ delete_command = command + " uninstall "
+
+ if not purge:
+ delete_command += " --keep-history"
+
+ if disable_hook:
+ delete_command += " --no-hooks"
+
+ if wait:
+ delete_command += " --wait"
+
+ if wait_timeout is not None:
+ delete_command += " --timeout " + wait_timeout
+
+ delete_command += " " + release_name
+
+ return delete_command
+
+
+def load_values_files(values_files):
+ values = {}
+ for values_file in values_files or []:
+ with open(values_file, "r") as fd:
+ content = yaml.safe_load(fd)
+ if not isinstance(content, dict):
+ continue
+ for k, v in content.items():
+ values[k] = v
+ return values
+
+
+def get_plugin_version(plugin):
+ """
+ Check if helm plugin is installed and return corresponding version
+ """
+
+ rc, output, err, command = module.get_helm_plugin_list()
+ out = parse_helm_plugin_list(output=output.splitlines())
+
+ if not out:
+ return None
+
+ for line in out:
+ if line[0] == plugin:
+ return line[1]
+ return None
+
+
+def helmdiff_check(
+ module,
+ release_name,
+ chart_ref,
+ release_values,
+ values_files=None,
+ chart_version=None,
+ replace=False,
+ chart_repo_url=None,
+):
+ """
+ Use helm diff to determine if a release would change by upgrading a chart.
+ """
+ cmd = module.get_helm_binary() + " diff upgrade"
+ cmd += " " + release_name
+ cmd += " " + chart_ref
+
+ if chart_repo_url is not None:
+ cmd += " " + "--repo=" + chart_repo_url
+ if chart_version is not None:
+ cmd += " " + "--version=" + chart_version
+ if not replace:
+ cmd += " " + "--reset-values"
+
+ if release_values != {}:
+ fd, path = tempfile.mkstemp(suffix=".yml")
+ with open(path, "w") as yaml_file:
+ yaml.dump(release_values, yaml_file, default_flow_style=False)
+ cmd += " -f=" + path
+ module.add_cleanup_file(path)
+
+ if values_files:
+ for values_file in values_files:
+ cmd += " -f=" + values_file
+
+ rc, out, err = module.run_helm_command(cmd)
+ return (len(out.strip()) > 0, out.strip())
+
+
+def default_check(release_status, chart_info, values=None, values_files=None):
+ """
+ Use default check to determine if release would change by upgrading a chart.
+ """
+ # the 'appVersion' specification is optional in a chart
+ chart_app_version = chart_info.get("appVersion", None)
+ released_app_version = release_status.get("app_version", None)
+
+ # when deployed without an 'appVersion' chart value the 'helm list' command will return the entry `app_version: ""`
+ appversion_is_same = (chart_app_version == released_app_version) or (
+ chart_app_version is None and released_app_version == ""
+ )
+
+ if values_files:
+ values_match = release_status["values"] == load_values_files(values_files)
+ else:
+ values_match = release_status["values"] == values
+ return (
+ not values_match
+ or (chart_info["name"] + "-" + chart_info["version"]) != release_status["chart"]
+ or not appversion_is_same
+ )
+
+
+def argument_spec():
+ arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
+ arg_spec.update(
+ dict(
+ chart_ref=dict(type="path"),
+ chart_repo_url=dict(type="str"),
+ chart_version=dict(type="str"),
+ dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
+ release_name=dict(type="str", required=True, aliases=["name"]),
+ release_namespace=dict(type="str", required=True, aliases=["namespace"]),
+ release_state=dict(
+ default="present", choices=["present", "absent"], aliases=["state"]
+ ),
+ release_values=dict(type="dict", default={}, aliases=["values"]),
+ values_files=dict(type="list", default=[], elements="str"),
+ update_repo_cache=dict(type="bool", default=False),
+ disable_hook=dict(type="bool", default=False),
+ force=dict(type="bool", default=False),
+ purge=dict(type="bool", default=True),
+ wait=dict(type="bool", default=False),
+ wait_timeout=dict(type="str"),
+ timeout=dict(type="str"),
+ atomic=dict(type="bool", default=False),
+ create_namespace=dict(type="bool", default=False),
+ post_renderer=dict(type="str"),
+ replace=dict(type="bool", default=False),
+ skip_crds=dict(type="bool", default=False),
+ history_max=dict(type="int"),
+ set_values=dict(type="list", elements="dict"),
+ )
+ )
+ return arg_spec
+
+
+def main():
+ global module
+ module = AnsibleHelmModule(
+ argument_spec=argument_spec(),
+ required_if=[
+ ("release_state", "present", ["release_name", "chart_ref"]),
+ ("release_state", "absent", ["release_name"]),
+ ],
+ mutually_exclusive=[
+ ("context", "ca_cert"),
+ ("replace", "history_max"),
+ ("wait_timeout", "timeout"),
+ ],
+ supports_check_mode=True,
+ )
+
+ if not IMP_YAML:
+ module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
+
+ changed = False
+
+ chart_ref = module.params.get("chart_ref")
+ chart_repo_url = module.params.get("chart_repo_url")
+ chart_version = module.params.get("chart_version")
+ dependency_update = module.params.get("dependency_update")
+ release_name = module.params.get("release_name")
+ release_state = module.params.get("release_state")
+ release_values = module.params.get("release_values")
+ values_files = module.params.get("values_files")
+ update_repo_cache = module.params.get("update_repo_cache")
+
+ # Helm options
+ disable_hook = module.params.get("disable_hook")
+ force = module.params.get("force")
+ purge = module.params.get("purge")
+ wait = module.params.get("wait")
+ wait_timeout = module.params.get("wait_timeout")
+ atomic = module.params.get("atomic")
+ create_namespace = module.params.get("create_namespace")
+ post_renderer = module.params.get("post_renderer")
+ replace = module.params.get("replace")
+ skip_crds = module.params.get("skip_crds")
+ history_max = module.params.get("history_max")
+ timeout = module.params.get("timeout")
+ set_values = module.params.get("set_values")
+
+ if update_repo_cache:
+ run_repo_update(module)
+
+ # Get real/deployed release status
+ release_status = get_release_status(module, release_name)
+
+ helm_cmd = module.get_helm_binary()
+ opt_result = {}
+ if release_state == "absent" and release_status is not None:
+ if replace:
+ module.fail_json(msg="replace is not applicable when state is absent")
+
+ if wait:
+ helm_version = module.get_helm_version()
+ if LooseVersion(helm_version) < LooseVersion("3.7.0"):
+ opt_result["warnings"] = []
+ opt_result["warnings"].append(
+ "helm uninstall support option --wait for helm release >= 3.7.0"
+ )
+ wait = False
+
+ helm_cmd = delete(
+ helm_cmd, release_name, purge, disable_hook, wait, wait_timeout
+ )
+ changed = True
+ elif release_state == "present":
+
+ if chart_version is not None:
+ helm_cmd += " --version=" + chart_version
+
+ if chart_repo_url is not None:
+ helm_cmd += " --repo=" + chart_repo_url
+
+ # Fetch chart info to have real version and real name for chart_ref from archive, folder or url
+ chart_info = fetch_chart_info(module, helm_cmd, chart_ref)
+
+ if dependency_update:
+ if chart_info.get("dependencies"):
+ # Can't use '--dependency-update' with 'helm upgrade' that is the
+ # default chart install method, so if chart_repo_url is defined
+ # we can't use the dependency update command. But, in the near future
+ # we can get rid of this method and use only '--dependency-update'
+ # option. Please see https://github.com/helm/helm/pull/8810
+ if not chart_repo_url and not re.fullmatch(
+ r"^http[s]*://[\w.:/?&=-]+$", chart_ref
+ ):
+ run_dep_update(module, chart_ref)
+
+ # To not add --dependency-update option in the deploy function
+ dependency_update = False
+ else:
+ module.warn(
+ "This is a not stable feature with 'chart_repo_url'. Please consider to use dependency update with on-disk charts"
+ )
+ if not replace:
+ msg_fail = (
+ "'--dependency-update' hasn't been supported yet with 'helm upgrade'. "
+ "Please use 'helm install' instead by adding 'replace' option"
+ )
+ module.fail_json(msg=msg_fail)
+ else:
+ module.warn(
+ "There is no dependencies block defined in Chart.yaml. Dependency update will not be performed. "
+ "Please consider add dependencies block or disable dependency_update to remove this warning."
+ )
+
+ if release_status is None: # Not installed
+ set_value_args = None
+ if set_values:
+ set_value_args = module.get_helm_set_values_args(set_values)
+
+ helm_cmd = deploy(
+ helm_cmd,
+ release_name,
+ release_values,
+ chart_ref,
+ wait,
+ wait_timeout,
+ disable_hook,
+ False,
+ values_files=values_files,
+ atomic=atomic,
+ create_namespace=create_namespace,
+ post_renderer=post_renderer,
+ replace=replace,
+ dependency_update=dependency_update,
+ skip_crds=skip_crds,
+ history_max=history_max,
+ timeout=timeout,
+ set_value_args=set_value_args,
+ )
+ changed = True
+
+ else:
+
+ helm_diff_version = get_plugin_version("diff")
+ if helm_diff_version and (
+ not chart_repo_url
+ or (
+ chart_repo_url
+ and LooseVersion(helm_diff_version) >= LooseVersion("3.4.1")
+ )
+ ):
+ (would_change, prepared) = helmdiff_check(
+ module,
+ release_name,
+ chart_ref,
+ release_values,
+ values_files,
+ chart_version,
+ replace,
+ chart_repo_url,
+ )
+ if would_change and module._diff:
+ opt_result["diff"] = {"prepared": prepared}
+ else:
+ module.warn(
+ "The default idempotency check can fail to report changes in certain cases. "
+ "Install helm diff >= 3.4.1 for better results."
+ )
+ would_change = default_check(
+ release_status, chart_info, release_values, values_files
+ )
+
+ if force or would_change:
+ set_value_args = None
+ if set_values:
+ set_value_args = module.get_helm_set_values_args(set_values)
+
+ helm_cmd = deploy(
+ helm_cmd,
+ release_name,
+ release_values,
+ chart_ref,
+ wait,
+ wait_timeout,
+ disable_hook,
+ force,
+ values_files=values_files,
+ atomic=atomic,
+ create_namespace=create_namespace,
+ post_renderer=post_renderer,
+ replace=replace,
+ skip_crds=skip_crds,
+ history_max=history_max,
+ timeout=timeout,
+ dependency_update=dependency_update,
+ set_value_args=set_value_args,
+ )
+ changed = True
+
+ if module.check_mode:
+ check_status = {"values": {"current": {}, "declared": {}}}
+ if release_status:
+ check_status["values"]["current"] = release_status["values"]
+ check_status["values"]["declared"] = release_status
+
+ module.exit_json(
+ changed=changed,
+ command=helm_cmd,
+ status=check_status,
+ stdout="",
+ stderr="",
+ **opt_result,
+ )
+ elif not changed:
+ module.exit_json(
+ changed=False,
+ status=release_status,
+ stdout="",
+ stderr="",
+ command=helm_cmd,
+ **opt_result,
+ )
+
+ rc, out, err = module.run_helm_command(helm_cmd)
+
+ module.exit_json(
+ changed=changed,
+ stdout=out,
+ stderr=err,
+ status=get_release_status(module, release_name),
+ command=helm_cmd,
+ **opt_result,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_info.py b/ansible_collections/kubernetes/core/plugins/modules/helm_info.py
new file mode 100644
index 00000000..aefd4477
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_info.py
@@ -0,0 +1,252 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_info
+
+short_description: Get information from Helm package deployed inside the cluster
+
+version_added: "0.11.0"
+
+author:
+ - Lucas Boisserie (@LucasBoisserie)
+
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+ - "yaml (https://pypi.org/project/PyYAML/)"
+
+description:
+ - Get information (values, states, ...) from Helm package deployed inside the cluster.
+
+options:
+ release_name:
+ description:
+ - Release name to manage.
+ required: true
+ type: str
+ aliases: [ name ]
+ release_namespace:
+ description:
+ - Kubernetes namespace where the chart should be installed.
+ required: true
+ type: str
+ aliases: [ namespace ]
+ release_state:
+ description:
+ - Show releases as per their states.
+ - Default value is C(deployed) and C(failed).
+ - If set to C(all), show all releases without any filter applied.
+ - If set to C(deployed), show deployed releases.
+ - If set to C(failed), show failed releases.
+ - If set to C(pending), show pending releases.
+ - If set to C(superseded), show superseded releases.
+ - If set to C(uninstalled), show uninstalled releases, if C(helm uninstall --keep-history) was used.
+ - If set to C(uninstalling), show releases that are currently being uninstalled.
+ type: list
+ elements: str
+ version_added: "2.3.0"
+ get_all_values:
+ description:
+ - Set to C(True) if you want to get all (computed) values of the release.
+ - When C(False) (default), only user supplied values are returned.
+ required: false
+ default: false
+ type: bool
+ version_added: "2.4.0"
+extends_documentation_fragment:
+ - kubernetes.core.helm_common_options
+"""
+
+EXAMPLES = r"""
+- name: Gather information of Grafana chart inside monitoring namespace
+ kubernetes.core.helm_info:
+ name: test
+ release_namespace: monitoring
+
+- name: Gather information about test-chart with pending state
+ kubernetes.core.helm_info:
+ name: test-chart
+ release_namespace: testenv
+ release_state:
+ - pending
+"""
+
+RETURN = r"""
+status:
+ type: complex
+ description: A dictionary of status output
+ returned: only when release exists
+ contains:
+ app_version:
+ type: str
+ returned: always
+ description: Version of app deployed
+ chart:
+ type: str
+ returned: always
+ description: Chart name and chart version
+ name:
+ type: str
+ returned: always
+ description: Name of the release
+ namespace:
+ type: str
+ returned: always
+ description: Namespace where the release is deployed
+ revision:
+ type: str
+ returned: always
+ description: Number of time where the release has been updated
+ status:
+ type: str
+ returned: always
+ description: Status of release (can be DEPLOYED, FAILED, ...)
+ updated:
+ type: str
+ returned: always
+ description: The Date of last update
+ values:
+ type: str
+ returned: always
+ description: Dict of Values used to deploy
+ hooks:
+ type: list
+ elements: dict
+ description: Hooks of the release
+ returned: always
+ version_added: "2.4.0"
+ notes:
+ type: str
+ description: Notes of the release
+ returned: always
+ version_added: "2.4.0"
+ manifest:
+ type: list
+ elements: dict
+ description: Manifest of the release
+ returned: always
+ version_added: "2.4.0"
+"""
+
+import traceback
+import copy
+
+try:
+ import yaml
+
+ IMP_YAML = True
+ IMP_YAML_ERR = None
+except ImportError:
+ IMP_YAML_ERR = traceback.format_exc()
+ IMP_YAML = False
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
+ HELM_AUTH_ARG_SPEC,
+ HELM_AUTH_MUTUALLY_EXCLUSIVE,
+)
+
+
+# Get Release from all deployed releases
+def get_release(state, release_name):
+ if state is not None:
+ for release in state:
+ if release["name"] == release_name:
+ return release
+ return None
+
+
+# Get Release state from deployed release
+def get_release_status(module, release_name, release_state, get_all_values=False):
+ list_command = module.get_helm_binary() + " list --output=yaml"
+
+ valid_release_states = [
+ "all",
+ "deployed",
+ "failed",
+ "pending",
+ "superseded",
+ "uninstalled",
+ "uninstalling",
+ ]
+
+ for local_release_state in release_state:
+ if local_release_state in valid_release_states:
+ list_command += " --%s" % local_release_state
+
+ list_command += " --filter " + release_name
+ rc, out, err = module.run_helm_command(list_command)
+
+ if rc != 0:
+ module.fail_json(
+ msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
+ rc, out, err
+ ),
+ command=list_command,
+ )
+
+ release = get_release(yaml.safe_load(out), release_name)
+
+ if release is None: # not install
+ return None
+
+ release["values"] = module.get_values(release_name, get_all_values)
+ release["manifest"] = module.get_manifest(release_name)
+ release["notes"] = module.get_notes(release_name)
+ release["hooks"] = module.get_hooks(release_name)
+
+ return release
+
+
+def argument_spec():
+ arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
+ arg_spec.update(
+ dict(
+ release_name=dict(type="str", required=True, aliases=["name"]),
+ release_namespace=dict(type="str", required=True, aliases=["namespace"]),
+ release_state=dict(type="list", default=[], elements="str"),
+ get_all_values=dict(type="bool", required=False, default=False),
+ )
+ )
+ return arg_spec
+
+
+def main():
+ global module
+
+ module = AnsibleHelmModule(
+ argument_spec=argument_spec(),
+ mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
+ supports_check_mode=True,
+ )
+
+ if not IMP_YAML:
+ module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
+
+ release_name = module.params.get("release_name")
+ release_state = module.params.get("release_state")
+ get_all_values = module.params.get("get_all_values")
+
+ release_status = get_release_status(
+ module, release_name, release_state, get_all_values
+ )
+
+ if release_status is not None:
+ module.exit_json(changed=False, status=release_status)
+
+ module.exit_json(changed=False)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_plugin.py b/ansible_collections/kubernetes/core/plugins/modules/helm_plugin.py
new file mode 100644
index 00000000..795dbf29
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_plugin.py
@@ -0,0 +1,322 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_plugin
+short_description: Manage Helm plugins
+version_added: "1.0.0"
+author:
+ - Abhijeet Kasurde (@Akasurde)
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+description:
+ - Manages Helm plugins.
+options:
+#Helm options
+ state:
+ description:
+ - If C(state=present) the Helm plugin will be installed.
+ - If C(state=latest) the Helm plugin will be updated. Added in version 2.3.0.
+ - If C(state=absent) the Helm plugin will be removed.
+ choices: [ absent, present, latest ]
+ default: present
+ type: str
+ plugin_name:
+ description:
+ - Name of Helm plugin.
+ - Required only if C(state=absent) or C(state=latest).
+ type: str
+ plugin_path:
+ description:
+ - Plugin path to a plugin on your local file system or a url of a remote VCS repo.
+ - If plugin path from file system is provided, make sure that tar is present on remote
+ machine and not on Ansible controller.
+ - Required only if C(state=present).
+ type: str
+ plugin_version:
+ description:
+ - Plugin version to install. If this is not specified, the latest version is installed.
+ - Ignored when C(state=absent) or C(state=latest).
+ required: false
+ type: str
+ version_added: "2.3.0"
+extends_documentation_fragment:
+ - kubernetes.core.helm_common_options
+"""
+
+EXAMPLES = r"""
+- name: Install Helm env plugin
+ kubernetes.core.helm_plugin:
+ plugin_path: https://github.com/adamreese/helm-env
+ state: present
+
+- name: Install Helm plugin from local filesystem
+ kubernetes.core.helm_plugin:
+ plugin_path: https://domain/path/to/plugin.tar.gz
+ state: present
+
+- name: Remove Helm env plugin
+ kubernetes.core.helm_plugin:
+ plugin_name: env
+ state: absent
+
+- name: Install Helm plugin with a specific version
+ kubernetes.core.helm_plugin:
+ plugin_version: 2.0.1
+ plugin_path: https://domain/path/to/plugin.tar.gz
+ state: present
+
+- name: Update Helm plugin
+ kubernetes.core.helm_plugin:
+ plugin_name: secrets
+ state: latest
+"""
+
+RETURN = r"""
+stdout:
+ type: str
+ description: Full `helm` command stdout, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+stderr:
+ type: str
+ description: Full `helm` command stderr, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+command:
+ type: str
+ description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: helm plugin list ...
+msg:
+ type: str
+ description: Info about successful command
+ returned: always
+ sample: "Plugin installed successfully"
+rc:
+ type: int
+ description: Helm plugin command return code
+ returned: always
+ sample: 1
+"""
+
+import copy
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+ parse_helm_plugin_list,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
+ HELM_AUTH_ARG_SPEC,
+ HELM_AUTH_MUTUALLY_EXCLUSIVE,
+)
+
+
+def argument_spec():
+ arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
+ arg_spec.update(
+ dict(
+ plugin_path=dict(
+ type="str",
+ ),
+ plugin_name=dict(
+ type="str",
+ ),
+ plugin_version=dict(
+ type="str",
+ ),
+ state=dict(
+ type="str",
+ default="present",
+ choices=["present", "absent", "latest"],
+ ),
+ )
+ )
+ return arg_spec
+
+
+def mutually_exclusive():
+ mutually_ex = copy.deepcopy(HELM_AUTH_MUTUALLY_EXCLUSIVE)
+ mutually_ex.append(("plugin_name", "plugin_path"))
+ return mutually_ex
+
+
+def main():
+ module = AnsibleHelmModule(
+ argument_spec=argument_spec(),
+ supports_check_mode=True,
+ required_if=[
+ ("state", "present", ("plugin_path",)),
+ ("state", "absent", ("plugin_name",)),
+ ("state", "latest", ("plugin_name",)),
+ ],
+ mutually_exclusive=mutually_exclusive(),
+ )
+
+ state = module.params.get("state")
+
+ helm_cmd_common = module.get_helm_binary() + " plugin"
+
+ if state == "present":
+ helm_cmd_common += " install %s" % module.params.get("plugin_path")
+ plugin_version = module.params.get("plugin_version")
+ if plugin_version is not None:
+ helm_cmd_common += " --version=%s" % plugin_version
+ if not module.check_mode:
+ rc, out, err = module.run_helm_command(
+ helm_cmd_common, fails_on_error=False
+ )
+ else:
+ rc, out, err = (0, "", "")
+
+ if rc == 1 and "plugin already exists" in err:
+ module.exit_json(
+ failed=False,
+ changed=False,
+ msg="Plugin already exists",
+ command=helm_cmd_common,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ elif rc == 0:
+ module.exit_json(
+ failed=False,
+ changed=True,
+ msg="Plugin installed successfully",
+ command=helm_cmd_common,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ else:
+ module.fail_json(
+ msg="Failure when executing Helm command.",
+ command=helm_cmd_common,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ elif state == "absent":
+ plugin_name = module.params.get("plugin_name")
+ rc, output, err, command = module.get_helm_plugin_list()
+ out = parse_helm_plugin_list(output=output.splitlines())
+
+ if not out:
+ module.exit_json(
+ failed=False,
+ changed=False,
+ msg="Plugin not found or is already uninstalled",
+ command=command,
+ stdout=output,
+ stderr=err,
+ rc=rc,
+ )
+
+ found = False
+ for line in out:
+ if line[0] == plugin_name:
+ found = True
+ break
+ if not found:
+ module.exit_json(
+ failed=False,
+ changed=False,
+ msg="Plugin not found or is already uninstalled",
+ command=command,
+ stdout=output,
+ stderr=err,
+ rc=rc,
+ )
+
+ helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
+ if not module.check_mode:
+ rc, out, err = module.run_helm_command(
+ helm_uninstall_cmd, fails_on_error=False
+ )
+ else:
+ rc, out, err = (0, "", "")
+
+ if rc == 0:
+ module.exit_json(
+ changed=True,
+ msg="Plugin uninstalled successfully",
+ command=helm_uninstall_cmd,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ module.fail_json(
+ msg="Failed to get Helm plugin uninstall",
+ command=helm_uninstall_cmd,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ elif state == "latest":
+ plugin_name = module.params.get("plugin_name")
+ rc, output, err, command = module.get_helm_plugin_list()
+ out = parse_helm_plugin_list(output=output.splitlines())
+
+ if not out:
+ module.exit_json(
+ failed=False,
+ changed=False,
+ msg="Plugin not found",
+ command=command,
+ stdout=output,
+ stderr=err,
+ rc=rc,
+ )
+
+ found = False
+ for line in out:
+ if line[0] == plugin_name:
+ found = True
+ break
+ if not found:
+ module.exit_json(
+ failed=False,
+ changed=False,
+ msg="Plugin not found",
+ command=command,
+ stdout=output,
+ stderr=err,
+ rc=rc,
+ )
+
+ helm_update_cmd = "%s update %s" % (helm_cmd_common, plugin_name)
+ if not module.check_mode:
+ rc, out, err = module.run_helm_command(
+ helm_update_cmd, fails_on_error=False
+ )
+ else:
+ rc, out, err = (0, "", "")
+
+ if rc == 0:
+ module.exit_json(
+ changed=True,
+ msg="Plugin updated successfully",
+ command=helm_update_cmd,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ module.fail_json(
+ msg="Failed to get Helm plugin update",
+ command=helm_update_cmd,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_plugin_info.py b/ansible_collections/kubernetes/core/plugins/modules/helm_plugin_info.py
new file mode 100644
index 00000000..3b9fcd18
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_plugin_info.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_plugin_info
+short_description: Gather information about Helm plugins
+version_added: "1.0.0"
+author:
+ - Abhijeet Kasurde (@Akasurde)
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+description:
+ - Gather information about Helm plugins installed in namespace.
+options:
+#Helm options
+ plugin_name:
+ description:
+ - Name of Helm plugin, to gather particular plugin info.
+ type: str
+extends_documentation_fragment:
+ - kubernetes.core.helm_common_options
+"""
+
+EXAMPLES = r"""
+- name: Gather Helm plugin info
+ kubernetes.core.helm_plugin_info:
+
+- name: Gather Helm env plugin info
+ kubernetes.core.helm_plugin_info:
+ plugin_name: env
+"""
+
+RETURN = r"""
+stdout:
+ type: str
+ description: Full `helm` command stdout, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+stderr:
+ type: str
+ description: Full `helm` command stderr, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+command:
+ type: str
+ description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: helm plugin list ...
+plugin_list:
+ type: list
+ description: Helm plugin dict inside a list
+ returned: always
+ sample: {
+ "name": "env",
+ "version": "0.1.0",
+ "description": "Print out the helm environment."
+ }
+rc:
+ type: int
+ description: Helm plugin command return code
+ returned: always
+ sample: 1
+"""
+
+import copy
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ parse_helm_plugin_list,
+ AnsibleHelmModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
+ HELM_AUTH_ARG_SPEC,
+ HELM_AUTH_MUTUALLY_EXCLUSIVE,
+)
+
+
+def main():
+
+ argument_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
+ argument_spec.update(
+ dict(
+ plugin_name=dict(
+ type="str",
+ ),
+ )
+ )
+
+ module = AnsibleHelmModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
+ supports_check_mode=True,
+ )
+
+ plugin_name = module.params.get("plugin_name")
+
+ plugin_list = []
+
+ rc, output, err, command = module.get_helm_plugin_list()
+
+ out = parse_helm_plugin_list(output=output.splitlines())
+
+ for line in out:
+ if plugin_name is None:
+ plugin_list.append(
+ {"name": line[0], "version": line[1], "description": line[2]}
+ )
+ continue
+
+ if plugin_name == line[0]:
+ plugin_list.append(
+ {"name": line[0], "version": line[1], "description": line[2]}
+ )
+ break
+
+ module.exit_json(
+ changed=True,
+ command=command,
+ stdout=output,
+ stderr=err,
+ rc=rc,
+ plugin_list=plugin_list,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_pull.py b/ansible_collections/kubernetes/core/plugins/modules/helm_pull.py
new file mode 100644
index 00000000..03edb97e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_pull.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_pull
+short_description: download a chart from a repository and (optionally) unpack it in local directory.
+version_added: "2.4.0"
+author:
+ - Aubin Bikouo (@abikouo)
+description:
+ - Retrieve a package from a package repository, and download it locally.
+ - It can also be used to perform cryptographic verification of a chart without installing the chart.
+ - There are options for unpacking the chart after download.
+
+requirements:
+ - "helm >= 3.0 (https://github.com/helm/helm/releases)"
+
+options:
+ chart_ref:
+ description:
+ - chart name on chart repository.
+ - absolute URL.
+ required: true
+ type: str
+ chart_version:
+ description:
+ - Specify a version constraint for the chart version to use.
+ - This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0).
+ - Mutually exclusive with C(chart_devel).
+ type: str
+ verify_chart:
+ description:
+ - Verify the package before using it.
+ default: False
+ type: bool
+ verify_chart_keyring:
+ description:
+ - location of public keys used for verification.
+ type: path
+ provenance:
+ description:
+ - Fetch the provenance file, but don't perform verification.
+ type: bool
+ default: False
+ repo_url:
+ description:
+ - chart repository url where to locate the requested chart.
+ type: str
+ aliases: [ url, chart_repo_url ]
+ repo_username:
+ description:
+ - Chart repository username where to locate the requested chart.
+ - Required if C(repo_password) is specified.
+ type: str
+ aliases: [ username, chart_repo_username ]
+ repo_password:
+ description:
+ - Chart repository password where to locate the requested chart.
+ - Required if C(repo_username) is specified.
+ type: str
+ aliases: [ password, chart_repo_password ]
+ pass_credentials:
+ description:
+ - Pass credentials to all domains.
+ default: False
+ type: bool
+ skip_tls_certs_check:
+ description:
+ - Whether or not to check tls certificate for the chart download.
+ - Requires helm >= 3.3.0.
+ type: bool
+ default: False
+ chart_devel:
+ description:
+ - Use development versions, too. Equivalent to version '>0.0.0-0'.
+ - Mutually exclusive with C(chart_version).
+ type: bool
+ untar_chart:
+ description:
+ - if set to true, will untar the chart after downloading it.
+ type: bool
+ default: False
+ destination:
+ description:
+ - location to write the chart.
+ type: path
+ required: True
+ chart_ca_cert:
+ description:
+ - Verify certificates of HTTPS-enabled servers using this CA bundle.
+ - Requires helm >= 3.1.0.
+ type: path
+ chart_ssl_cert_file:
+ description:
+ - Identify HTTPS client using this SSL certificate file.
+ - Requires helm >= 3.1.0.
+ type: path
+ chart_ssl_key_file:
+ description:
+ - Identify HTTPS client using this SSL key file
+ - Requires helm >= 3.1.0.
+ type: path
+ binary_path:
+ description:
+ - The path of a helm binary to use.
+ required: false
+ type: path
+"""
+
+EXAMPLES = r"""
+- name: Download chart using chart url
+ kubernetes.core.helm_pull:
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: /path/to/chart
+
+- name: Download Chart using chart_name and repo_url
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+
+- name: Download Chart (skip tls certificate check)
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+ skip_tls_certs_check: yes
+
+- name: Download Chart using chart registry credentials
+ kubernetes.core.helm_pull:
+ chart_ref: redis
+ repo_url: https://charts.bitnami.com/bitnami
+ untar_chart: yes
+ destination: /path/to/chart
+ username: myuser
+ password: mypassword123
+"""
+
+RETURN = r"""
+stdout:
+ type: str
+ description: Full `helm pull` command stdout, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+stderr:
+ type: str
+ description: Full `helm pull` command stderr, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+command:
+ type: str
+ description: Full `helm pull` command built by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: helm pull --repo test ...
+rc:
+ type: int
+ description: Helm pull command return code
+ returned: always
+ sample: 1
+"""
+
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+
+def main():
+ argspec = dict(
+ chart_ref=dict(type="str", required=True),
+ chart_version=dict(type="str"),
+ verify_chart=dict(type="bool", default=False),
+ verify_chart_keyring=dict(type="path"),
+ provenance=dict(type="bool", default=False),
+ repo_url=dict(type="str", aliases=["url", "chart_repo_url"]),
+ repo_username=dict(type="str", aliases=["username", "chart_repo_username"]),
+ repo_password=dict(
+ type="str", no_log=True, aliases=["password", "chart_repo_password"]
+ ),
+ pass_credentials=dict(type="bool", default=False),
+ skip_tls_certs_check=dict(type="bool", default=False),
+ chart_devel=dict(type="bool"),
+ untar_chart=dict(type="bool", default=False),
+ destination=dict(type="path", required=True),
+ chart_ca_cert=dict(type="path"),
+ chart_ssl_cert_file=dict(type="path"),
+ chart_ssl_key_file=dict(type="path"),
+ binary_path=dict(type="path"),
+ )
+ module = AnsibleHelmModule(
+ argument_spec=argspec,
+ supports_check_mode=True,
+ required_by=dict(
+ repo_username=("repo_password"),
+ repo_password=("repo_username"),
+ ),
+ mutually_exclusive=[("chart_version", "chart_devel")],
+ )
+
+ helm_version = module.get_helm_version()
+ if LooseVersion(helm_version) < LooseVersion("3.0.0"):
+ module.fail_json(
+ msg="This module requires helm >= 3.0.0, current version is {0}".format(
+ helm_version
+ )
+ )
+
+ helm_pull_opt_versionning = dict(
+ skip_tls_certs_check="3.3.0",
+ chart_ca_cert="3.1.0",
+ chart_ssl_cert_file="3.1.0",
+ chart_ssl_key_file="3.1.0",
+ )
+
+ def test_version_requirement(opt):
+ req_version = helm_pull_opt_versionning.get(opt)
+ if req_version and LooseVersion(helm_version) < LooseVersion(req_version):
+ module.fail_json(
+ msg="Parameter {0} requires helm >= {1}, current version is {2}".format(
+ opt, req_version, helm_version
+ )
+ )
+
+ # Set `helm pull` arguments requiring values
+ helm_pull_opts = []
+
+ helm_value_args = dict(
+ chart_version="version",
+ verify_chart_keyring="keyring",
+ repo_url="repo",
+ repo_username="username",
+ repo_password="password",
+ destination="destination",
+ chart_ca_cert="ca-file",
+ chart_ssl_cert_file="cert-file",
+ chart_ssl_key_file="key-file",
+ )
+
+ for opt, cmdkey in helm_value_args.items():
+ if module.params.get(opt):
+ test_version_requirement(opt)
+ helm_pull_opts.append("--{0} {1}".format(cmdkey, module.params.get(opt)))
+
+ # Set `helm pull` arguments flags
+ helm_flag_args = dict(
+ verify_chart=dict(key="verify"),
+ provenance=dict(key="prov"),
+ pass_credentials=dict(key="pass-credentials"),
+ skip_tls_certs_check=dict(key="insecure-skip-tls-verify"),
+ chart_devel=dict(key="devel"),
+ untar_chart=dict(key="untar"),
+ )
+
+ for k, v in helm_flag_args.items():
+ if module.params.get(k):
+ test_version_requirement(k)
+ helm_pull_opts.append("--{0}".format(v["key"]))
+
+ helm_cmd_common = "{0} pull {1} {2}".format(
+ module.get_helm_binary(),
+ module.params.get("chart_ref"),
+ " ".join(helm_pull_opts),
+ )
+ if not module.check_mode:
+ rc, out, err = module.run_helm_command(helm_cmd_common, fails_on_error=False)
+ else:
+ rc, out, err = (0, "", "")
+
+ if rc == 0:
+ module.exit_json(
+ failed=False,
+ changed=True,
+ command=helm_cmd_common,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+ else:
+ module.fail_json(
+ msg="Failure when executing Helm command.",
+ command=helm_cmd_common,
+ changed=False,
+ stdout=out,
+ stderr=err,
+ rc=rc,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_repository.py b/ansible_collections/kubernetes/core/plugins/modules/helm_repository.py
new file mode 100644
index 00000000..34213add
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_repository.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_repository
+
+short_description: Manage Helm repositories.
+
+version_added: "0.11.0"
+
+author:
+ - Lucas Boisserie (@LucasBoisserie)
+
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+ - "yaml (https://pypi.org/project/PyYAML/)"
+
+description:
+ - Manage Helm repositories.
+
+options:
+ binary_path:
+ description:
+ - The path of a helm binary to use.
+ required: false
+ type: path
+ repo_name:
+ description:
+ - Chart repository name.
+ required: true
+ type: str
+ aliases: [ name ]
+ repo_url:
+ description:
+ - Chart repository url
+ type: str
+ aliases: [ url ]
+ repo_username:
+ description:
+ - Chart repository username for repository with basic auth.
+ - Required if chart_repo_password is specified.
+ required: false
+ type: str
+ aliases: [ username ]
+ repo_password:
+ description:
+ - Chart repository password for repository with basic auth.
+ - Required if chart_repo_username is specified.
+ required: false
+ type: str
+ aliases: [ password ]
+ repo_state:
+ choices: ['present', 'absent']
+ description:
+ - Desired state of repository.
+ required: false
+ default: present
+ aliases: [ state ]
+ type: str
+ pass_credentials:
+ description:
+ - Pass credentials to all domains.
+ required: false
+ default: false
+ type: bool
+ version_added: 2.3.0
+ host:
+ description:
+ - Provide a URL for accessing the API. Can also be specified via C(K8S_AUTH_HOST) environment variable.
+ type: str
+ version_added: "2.3.0"
+ api_key:
+ description:
+ - Token used to authenticate with the API. Can also be specified via C(K8S_AUTH_API_KEY) environment variable.
+ type: str
+ version_added: "2.3.0"
+ validate_certs:
+ description:
+ - Whether or not to verify the API server's SSL certificates. Can also be specified via C(K8S_AUTH_VERIFY_SSL)
+ environment variable.
+ type: bool
+ aliases: [ verify_ssl ]
+ default: True
+ version_added: "2.3.0"
+ ca_cert:
+ description:
+ - Path to a CA certificate used to authenticate with the API. The full certificate chain must be provided to
+ avoid certificate validation errors. Can also be specified via C(K8S_AUTH_SSL_CA_CERT) environment variable.
+ type: path
+ aliases: [ ssl_ca_cert ]
+ version_added: "2.3.0"
+ context:
+ description:
+ - Helm option to specify which kubeconfig context to use.
+ - If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
+ type: str
+ aliases: [ kube_context ]
+ version_added: "2.4.0"
+ kubeconfig:
+ description:
+ - Helm option to specify kubeconfig path to use.
+ - If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
+ - The configuration can be provided as dictionary.
+ type: raw
+ aliases: [ kubeconfig_path ]
+ version_added: "2.4.0"
+ force_update:
+ description:
+ - Whether or not to replace (overwrite) the repo if it already exists.
+ type: bool
+ aliases: [ force ]
+ default: False
+ version_added: "2.4.0"
+"""
+
+EXAMPLES = r"""
+- name: Add a repository
+ kubernetes.core.helm_repository:
+ name: stable
+ repo_url: https://kubernetes.github.io/ingress-nginx
+
+- name: Add Red Hat Helm charts repository
+ kubernetes.core.helm_repository:
+ name: redhat-charts
+ repo_url: https://redhat-developer.github.com/redhat-helm-charts
+"""
+
+RETURN = r"""
+stdout:
+ type: str
+ description: Full `helm` command stdout, in case you want to display it or examine the event log
+ returned: always
+ sample: '"bitnami" has been added to your repositories'
+stdout_lines:
+ type: list
+ description: Full `helm` command stdout in list, in case you want to display it or examine the event log
+ returned: always
+ sample: ["\"bitnami\" has been added to your repositories"]
+stderr:
+ type: str
+ description: Full `helm` command stderr, in case you want to display it or examine the event log
+ returned: always
+ sample: ''
+stderr_lines:
+ type: list
+ description: Full `helm` command stderr in list, in case you want to display it or examine the event log
+ returned: always
+ sample: [""]
+command:
+ type: str
+ description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: '/usr/local/bin/helm repo add bitnami https://charts.bitnami.com/bitnami'
+msg:
+ type: str
+ description: Error message returned by `helm` command
+ returned: on failure
+ sample: 'Repository already have a repository named bitnami'
+"""
+
+import traceback
+import copy
+
+try:
+ import yaml
+
+ IMP_YAML = True
+ IMP_YAML_ERR = None
+except ImportError:
+ IMP_YAML_ERR = traceback.format_exc()
+ IMP_YAML = False
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.helm_args_common import (
+ HELM_AUTH_ARG_SPEC,
+ HELM_AUTH_MUTUALLY_EXCLUSIVE,
+)
+
+
+# Get repository from all repositories added
+def get_repository(state, repo_name):
+ if state is not None:
+ for repository in state:
+ if repository["name"] == repo_name:
+ return repository
+ return None
+
+
+# Get repository status
+def get_repository_status(module, repository_name):
+ list_command = module.get_helm_binary() + " repo list --output=yaml"
+
+ rc, out, err = module.run_helm_command(list_command, fails_on_error=False)
+
+ # no repo => rc=1 and 'no repositories to show' in output
+ if rc == 1 and "no repositories to show" in err:
+ return None
+ elif rc != 0:
+ module.fail_json(
+ msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
+ rc, out, err
+ ),
+ command=list_command,
+ )
+
+ return get_repository(yaml.safe_load(out), repository_name)
+
+
+# Install repository
+def install_repository(
+ command,
+ repository_name,
+ repository_url,
+ repository_username,
+ repository_password,
+ pass_credentials,
+ force_update,
+):
+ install_command = command + " repo add " + repository_name + " " + repository_url
+
+ if repository_username is not None and repository_password is not None:
+ install_command += " --username=" + repository_username
+ install_command += " --password=" + repository_password
+
+ if pass_credentials:
+ install_command += " --pass-credentials"
+
+ if force_update:
+ install_command += " --force-update"
+
+ return install_command
+
+
+# Delete repository
+def delete_repository(command, repository_name):
+ remove_command = command + " repo rm " + repository_name
+
+ return remove_command
+
+
+def argument_spec():
+ arg_spec = copy.deepcopy(HELM_AUTH_ARG_SPEC)
+ arg_spec.update(
+ dict(
+ repo_name=dict(type="str", aliases=["name"], required=True),
+ repo_url=dict(type="str", aliases=["url"]),
+ repo_username=dict(type="str", aliases=["username"]),
+ repo_password=dict(type="str", aliases=["password"], no_log=True),
+ repo_state=dict(
+ default="present", choices=["present", "absent"], aliases=["state"]
+ ),
+ pass_credentials=dict(type="bool", default=False, no_log=True),
+ force_update=dict(type="bool", default=False, aliases=["force"]),
+ )
+ )
+ return arg_spec
+
+
+def main():
+ global module
+
+ module = AnsibleHelmModule(
+ argument_spec=argument_spec(),
+ required_together=[["repo_username", "repo_password"]],
+ required_if=[("repo_state", "present", ["repo_url"])],
+ mutually_exclusive=HELM_AUTH_MUTUALLY_EXCLUSIVE,
+ supports_check_mode=True,
+ )
+
+ if not IMP_YAML:
+ module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
+
+ changed = False
+
+ repo_name = module.params.get("repo_name")
+ repo_url = module.params.get("repo_url")
+ repo_username = module.params.get("repo_username")
+ repo_password = module.params.get("repo_password")
+ repo_state = module.params.get("repo_state")
+ pass_credentials = module.params.get("pass_credentials")
+ force_update = module.params.get("force_update")
+
+ helm_cmd = module.get_helm_binary()
+
+ repository_status = get_repository_status(module, repo_name)
+
+ if repo_state == "absent" and repository_status is not None:
+ helm_cmd = delete_repository(helm_cmd, repo_name)
+ changed = True
+ elif repo_state == "present":
+ if repository_status is None or force_update:
+ helm_cmd = install_repository(
+ helm_cmd,
+ repo_name,
+ repo_url,
+ repo_username,
+ repo_password,
+ pass_credentials,
+ force_update,
+ )
+ changed = True
+ elif repository_status["url"] != repo_url:
+ module.fail_json(
+ msg="Repository already have a repository named {0}".format(repo_name)
+ )
+
+ if module.check_mode:
+ module.exit_json(changed=changed)
+ elif not changed:
+ module.exit_json(changed=False, repo_name=repo_name, repo_url=repo_url)
+
+ rc, out, err = module.run_helm_command(helm_cmd)
+
+ if repo_password is not None:
+ helm_cmd = helm_cmd.replace(repo_password, "******")
+
+ if rc != 0:
+ module.fail_json(
+ msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
+ rc, out, err
+ ),
+ command=helm_cmd,
+ )
+
+ module.exit_json(changed=changed, stdout=out, stderr=err, command=helm_cmd)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/helm_template.py b/ansible_collections/kubernetes/core/plugins/modules/helm_template.py
new file mode 100644
index 00000000..ab50f871
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/helm_template.py
@@ -0,0 +1,359 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+
+module: helm_template
+
+short_description: Render chart templates
+
+author:
+ - Mike Graves (@gravesm)
+
+description:
+ - Render chart templates to an output directory or as text of concatenated yaml documents.
+
+options:
+ binary_path:
+ description:
+ - The path of a helm binary to use.
+ required: false
+ type: path
+ chart_ref:
+ description:
+ - Chart reference with repo prefix, for example, C(nginx-stable/nginx-ingress).
+ - Path to a packaged chart.
+ - Path to an unpacked chart directory.
+ - Absolute URL.
+ required: true
+ type: path
+ chart_repo_url:
+ description:
+ - Chart repository URL where the requested chart is located.
+ required: false
+ type: str
+ chart_version:
+ description:
+ - Chart version to use. If this is not specified, the latest version is installed.
+ required: false
+ type: str
+ dependency_update:
+ description:
+ - Run helm dependency update before the operation.
+ - The I(dependency_update) option require the add of C(dependencies) block in C(Chart.yaml/requirements.yaml) file.
+ - For more information please visit U(https://helm.sh/docs/helm/helm_dependency/)
+ default: false
+ type: bool
+ aliases: [ dep_up ]
+ version_added: "2.4.0"
+ disable_hook:
+ description:
+ - Prevent hooks from running during install.
+ default: False
+ type: bool
+ version_added: 2.4.0
+ include_crds:
+ description:
+ - Include custom resource descriptions in rendered templates.
+ required: false
+ type: bool
+ default: false
+ output_dir:
+ description:
+ - Output directory where templates will be written.
+ - If the directory already exists, it will be overwritten.
+ required: false
+ type: path
+ release_name:
+ description:
+ - Release name to use in rendered templates.
+ required: false
+ aliases: [ name ]
+ type: str
+ version_added: 2.4.0
+ release_namespace:
+ description:
+ - namespace scope for this request.
+ required: false
+ type: str
+ version_added: 2.4.0
+ release_values:
+ description:
+ - Values to pass to chart.
+ required: false
+ default: {}
+ aliases: [ values ]
+ type: dict
+ show_only:
+ description:
+ - Only show manifests rendered from the given templates.
+ required: false
+ type: list
+ elements: str
+ version_added: 2.4.0
+ values_files:
+ description:
+ - Value files to pass to chart.
+ - Paths will be read from the target host's filesystem, not the host running ansible.
+ - I(values_files) option is evaluated before I(values) option if both are used.
+ - Paths are evaluated in the order the paths are specified.
+ required: false
+ default: []
+ type: list
+ elements: str
+ update_repo_cache:
+ description:
+ - Run C(helm repo update) before the operation. Can be run as part of the template generation or as a separate step.
+ default: false
+ type: bool
+ set_values:
+ description:
+ - Values to pass to chart configuration.
+ required: false
+ type: list
+ elements: dict
+ suboptions:
+ value:
+ description:
+ - Value to pass to chart configuration (e.g phase=prod).
+ type: str
+ required: true
+ value_type:
+ description:
+ - Use C(raw) set individual value.
+ - Use C(string) to force a string for an individual value.
+ - Use C(file) to set individual values from a file when the value itself is too long for the command line or is dynamically generated.
+ - Use C(json) to set json values (scalars/objects/arrays). This feature requires helm>=3.10.0.
+ default: raw
+ choices:
+ - raw
+ - string
+ - json
+ - file
+ version_added: '2.4.0'
+"""
+
+EXAMPLES = r"""
+- name: Render templates to specified directory
+ kubernetes.core.helm_template:
+ chart_ref: stable/prometheus
+ output_dir: mycharts
+
+- name: Render templates
+ kubernetes.core.helm_template:
+ chart_ref: stable/prometheus
+ register: result
+
+- name: Write templates to file
+ copy:
+ dest: myfile.yaml
+ content: "{{ result.stdout }}"
+
+- name: Render MutatingWebhooksConfiguration for revision tag "canary", rev "1-13-0"
+ kubernetes.core.helm_template:
+ chart_ref: istio/istiod
+ chart_version: "1.13.0"
+ release_namespace: "istio-system"
+ show_only:
+ - "templates/revision-tags.yaml"
+ release_values:
+ revision: "1-13-0"
+ revisionTags:
+ - "canary"
+ register: result
+
+- name: Write templates to file
+ copy:
+ dest: myfile.yaml
+ content: "{{ result.stdout }}"
+"""
+
+RETURN = r"""
+stdout:
+ type: str
+ description: Full C(helm) command stdout. If no I(output_dir) has been provided this will contain the rendered templates as concatenated yaml documents.
+ returned: always
+ sample: ''
+stderr:
+ type: str
+ description: Full C(helm) command stderr, in case you want to display it or examine the event log.
+ returned: always
+ sample: ''
+command:
+ type: str
+ description: Full C(helm) command run by this module, in case you want to re-run the command outside the module or debug a problem.
+ returned: always
+ sample: helm template --output-dir mychart nginx-stable/nginx-ingress
+"""
+
+import tempfile
+import traceback
+
+try:
+ import yaml
+
+ IMP_YAML = True
+ IMP_YAML_ERR = None
+except ImportError:
+ IMP_YAML_ERR = traceback.format_exc()
+ IMP_YAML = False
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+)
+
+
+def template(
+ cmd,
+ chart_ref,
+ chart_repo_url=None,
+ chart_version=None,
+ dependency_update=None,
+ disable_hook=None,
+ output_dir=None,
+ show_only=None,
+ release_name=None,
+ release_namespace=None,
+ release_values=None,
+ values_files=None,
+ include_crds=False,
+ set_values=None,
+):
+ cmd += " template "
+
+ if release_name:
+ cmd += release_name + " "
+
+ cmd += chart_ref
+
+ if dependency_update:
+ cmd += " --dependency-update"
+
+ if chart_repo_url:
+ cmd += " --repo=" + chart_repo_url
+
+ if chart_version:
+ cmd += " --version=" + chart_version
+
+ if disable_hook:
+ cmd += " --no-hooks"
+
+ if output_dir:
+ cmd += " --output-dir=" + output_dir
+
+ if show_only:
+ for template in show_only:
+ cmd += " -s " + template
+
+ if values_files:
+ for values_file in values_files:
+ cmd += " -f=" + values_file
+
+ if release_namespace:
+ cmd += " -n " + release_namespace
+
+ if release_values:
+ fd, path = tempfile.mkstemp(suffix=".yml")
+ with open(path, "w") as yaml_file:
+ yaml.dump(release_values, yaml_file, default_flow_style=False)
+ cmd += " -f=" + path
+
+ if include_crds:
+ cmd += " --include-crds"
+
+ if set_values:
+ cmd += " " + set_values
+
+ return cmd
+
+
+def main():
+ module = AnsibleHelmModule(
+ argument_spec=dict(
+ binary_path=dict(type="path"),
+ chart_ref=dict(type="path", required=True),
+ chart_repo_url=dict(type="str"),
+ chart_version=dict(type="str"),
+ dependency_update=dict(type="bool", default=False, aliases=["dep_up"]),
+ disable_hook=dict(type="bool", default=False),
+ include_crds=dict(type="bool", default=False),
+ release_name=dict(type="str", aliases=["name"]),
+ output_dir=dict(type="path"),
+ release_namespace=dict(type="str"),
+ release_values=dict(type="dict", default={}, aliases=["values"]),
+ show_only=dict(type="list", default=[], elements="str"),
+ values_files=dict(type="list", default=[], elements="str"),
+ update_repo_cache=dict(type="bool", default=False),
+ set_values=dict(type="list", elements="dict"),
+ ),
+ supports_check_mode=True,
+ )
+
+ check_mode = module.check_mode
+ chart_ref = module.params.get("chart_ref")
+ chart_repo_url = module.params.get("chart_repo_url")
+ chart_version = module.params.get("chart_version")
+ dependency_update = module.params.get("dependency_update")
+ disable_hook = module.params.get("disable_hook")
+ include_crds = module.params.get("include_crds")
+ release_name = module.params.get("release_name")
+ output_dir = module.params.get("output_dir")
+ show_only = module.params.get("show_only")
+ release_namespace = module.params.get("release_namespace")
+ release_values = module.params.get("release_values")
+ values_files = module.params.get("values_files")
+ update_repo_cache = module.params.get("update_repo_cache")
+ set_values = module.params.get("set_values")
+
+ if not IMP_YAML:
+ module.fail_json(msg=missing_required_lib("yaml"), exception=IMP_YAML_ERR)
+
+ helm_cmd = module.get_helm_binary()
+
+ if update_repo_cache:
+ update_cmd = helm_cmd + " repo update"
+ module.run_helm_command(update_cmd)
+
+ set_values_args = None
+ if set_values:
+ set_values_args = module.get_helm_set_values_args(set_values)
+
+ tmpl_cmd = template(
+ helm_cmd,
+ chart_ref,
+ dependency_update=dependency_update,
+ chart_repo_url=chart_repo_url,
+ chart_version=chart_version,
+ disable_hook=disable_hook,
+ release_name=release_name,
+ output_dir=output_dir,
+ release_namespace=release_namespace,
+ release_values=release_values,
+ show_only=show_only,
+ values_files=values_files,
+ include_crds=include_crds,
+ set_values=set_values_args,
+ )
+
+ if not check_mode:
+ rc, out, err = module.run_helm_command(tmpl_cmd)
+ else:
+ out = err = ""
+ rc = 0
+
+ module.exit_json(
+ failed=False, changed=True, command=tmpl_cmd, stdout=out, stderr=err, rc=rc
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s.py b/ansible_collections/kubernetes/core/plugins/modules/k8s.py
new file mode 100644
index 00000000..9b284d15
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s.py
@@ -0,0 +1,479 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2018, Chris Houseknecht <@chouseknecht>
+# (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
+
+short_description: Manage Kubernetes (K8s) objects
+
+author:
+ - "Chris Houseknecht (@chouseknecht)"
+ - "Fabian von Feilitzsch (@fabianvf)"
+
+description:
+ - Use the Kubernetes Python client to perform CRUD operations on K8s objects.
+ - Pass the object definition from a source file or inline. See examples for reading
+ files and using Jinja templates or vault-encrypted files.
+ - Access to the full range of K8s APIs.
+ - Use the M(kubernetes.core.k8s_info) module to obtain a list of items about an object of type C(kind)
+ - Authenticate using either a config file, certificates, password or token.
+ - Supports check mode.
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_name_options
+ - kubernetes.core.k8s_resource_options
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_wait_options
+ - kubernetes.core.k8s_delete_options
+
+options:
+ state:
+ description:
+ - Determines if an object should be created, patched, or deleted. When set to C(present), an object will be
+ created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to
+ C(present), an existing object will be patched, if its attributes differ from those specified using
+ I(resource_definition) or I(src).
+ - C(patched) state is an existing resource that has a given patch applied. If the resource doesn't exist, silently skip it (do not raise an error).
+ type: str
+ default: present
+ choices: [ absent, present, patched ]
+ force:
+ description:
+ - If set to C(yes), and I(state) is C(present), an existing object will be replaced.
+ type: bool
+ default: no
+ merge_type:
+ description:
+ - Whether to override the default patch merge approach with a specific type. By default, the strategic
+ merge will typically be used.
+ - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may
+ want to use C(merge) if you see "strategic merge patch format is not supported"
+ - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
+ - If more than one C(merge_type) is given, the merge_types will be tried in order. This defaults to
+ C(['strategic-merge', 'merge']), which is ideal for using the same parameters on resource kinds that
+ combine Custom Resources and built-in resources.
+ - mutually exclusive with C(apply)
+ - I(merge_type=json) is deprecated and will be removed in version 3.0.0. Please use M(kubernetes.core.k8s_json_patch) instead.
+ choices:
+ - json
+ - merge
+ - strategic-merge
+ type: list
+ elements: str
+ validate:
+ description:
+ - how (if at all) to validate the resource definition against the kubernetes schema.
+ Requires the kubernetes-validate python module.
+ suboptions:
+ fail_on_error:
+ description: whether to fail on validation errors.
+ type: bool
+ version:
+ description: version of Kubernetes to validate against. defaults to Kubernetes server version
+ type: str
+ strict:
+ description: whether to fail when passing unexpected properties
+ default: True
+ type: bool
+ type: dict
+ append_hash:
+ description:
+ - Whether to append a hash to a resource name for immutability purposes
+ - Applies only to ConfigMap and Secret resources
+ - The parameter will be silently ignored for other resource kinds
+ - The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash
+ will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including
+ the generated hash and append_hash=no)
+ default: False
+ type: bool
+ apply:
+ description:
+ - C(apply) compares the desired resource definition with the previously supplied resource definition,
+ ignoring properties that are automatically generated
+ - C(apply) works better with Services than 'force=yes'
+ - mutually exclusive with C(merge_type)
+ default: False
+ type: bool
+ template:
+ description:
+ - Provide a valid YAML template definition file for an object when creating or updating.
+ - Value can be provided as string or dictionary.
+ - The parameter accepts multiple template files. Added in version 2.0.0.
+ - Mutually exclusive with C(src) and C(resource_definition).
+ - Template files needs to be present on the Ansible Controller's file system.
+ - Additional parameters can be specified using dictionary.
+ - 'Valid additional parameters - '
+ - 'C(newline_sequence) (str): Specify the newline sequence to use for templating files.
+ valid choices are "\n", "\r", "\r\n". Default value "\n".'
+ - 'C(block_start_string) (str): The string marking the beginning of a block.
+ Default value "{%".'
+ - 'C(block_end_string) (str): The string marking the end of a block.
+ Default value "%}".'
+ - 'C(variable_start_string) (str): The string marking the beginning of a print statement.
+ Default value "{{".'
+ - 'C(variable_end_string) (str): The string marking the end of a print statement.
+ Default value "}}".'
+ - 'C(trim_blocks) (bool): Determine when newlines should be removed from blocks. When set to C(yes) the first newline
+ after a block is removed (block, not variable tag!). Default value is true.'
+ - 'C(lstrip_blocks) (bool): Determine when leading spaces and tabs should be stripped.
+ When set to C(yes) leading spaces and tabs are stripped from the start of a line to a block.
+ This functionality requires Jinja 2.7 or newer. Default value is false.'
+ type: raw
+ continue_on_error:
+ description:
+ - Whether to continue on creation/deletion errors when multiple resources are defined.
+ - This has no effect on the validation step which is controlled by the C(validate.fail_on_error) parameter.
+ type: bool
+ default: False
+ version_added: 2.0.0
+ label_selectors:
+ description:
+ - Selector (label query) to filter on.
+ type: list
+ elements: str
+ version_added: 2.2.0
+ generate_name:
+ description:
+ - Use to specify the basis of an object name and random characters will be added automatically on server to generate a unique name.
+ - This option is ignored when I(state) is not set to C(present) or when I(apply) is set to C(yes).
+ - If I(resource definition) is provided, the I(metadata.generateName) value from the I(resource_definition)
+ will override this option.
+ - If I(resource definition) is provided, and contains I(metadata.name), this option is ignored.
+ - mutually exclusive with C(name).
+ type: str
+ version_added: 2.3.0
+ server_side_apply:
+ description:
+ - When this option is set, apply runs in the server instead of the client.
+ - Ignored if C(apply) is not set or is set to False.
+ - This option requires "kubernetes >= 19.15.0".
+ type: dict
+ version_added: 2.3.0
+ suboptions:
+ field_manager:
+ type: str
+ description:
+ - Name of the manager used to track field ownership.
+ required: True
+ force_conflicts:
+ description:
+ - A conflict is a special status error that occurs when an Server Side Apply operation tries to change a field,
+ which another user also claims to manage.
+ - When set to True, server-side apply will force the changes against conflicts.
+ type: bool
+ default: False
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+ - "jsonpatch"
+"""
+
+EXAMPLES = r"""
+- name: Create a k8s namespace
+ kubernetes.core.k8s:
+ name: testing
+ api_version: v1
+ kind: Namespace
+ state: present
+
+- name: Create a Service object from an inline definition
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: web
+ namespace: testing
+ labels:
+ app: galaxy
+ service: web
+ spec:
+ selector:
+ app: galaxy
+ service: web
+ ports:
+ - protocol: TCP
+ targetPort: 8000
+ name: port-8000-tcp
+ port: 8000
+
+- name: Remove an existing Service object
+ kubernetes.core.k8s:
+ state: absent
+ api_version: v1
+ kind: Service
+ namespace: testing
+ name: web
+
+# Passing the object definition from a file
+
+- name: Create a Deployment by reading the definition from a local file
+ kubernetes.core.k8s:
+ state: present
+ src: /testing/deployment.yml
+
+- name: >-
+ Read definition file from the Ansible controller file system.
+ If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
+ kubernetes.core.k8s:
+ state: present
+ definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
+
+- name: >-
+ (Alternative) Read definition file from the Ansible controller file system.
+ In this case, the definition file contains multiple YAML documents, separated by ---.
+ If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
+ kubernetes.core.k8s:
+ state: present
+ definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml_all }}"
+
+- name: Read definition template file from the Ansible controller file system
+ kubernetes.core.k8s:
+ state: present
+ template: '/testing/deployment.j2'
+
+- name: Read definition template file from the Ansible controller file system that uses custom start/end strings
+ kubernetes.core.k8s:
+ state: present
+ template:
+ path: '/testing/deployment.j2'
+ 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
+ definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
+ validate:
+ fail_on_error: yes
+
+- name: warn on validation errors, check for unexpected properties
+ kubernetes.core.k8s:
+ state: present
+ definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
+ validate:
+ fail_on_error: no
+ strict: yes
+
+# Download and apply manifest
+- name: Download metrics-server manifest to the cluster.
+ ansible.builtin.get_url:
+ url: https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+ dest: ~/metrics-server.yaml
+ mode: '0664'
+
+- name: Apply metrics-server manifest to the cluster.
+ kubernetes.core.k8s:
+ state: present
+ src: ~/metrics-server.yaml
+
+# Wait for a Deployment to pause before continuing
+- name: Pause a Deployment.
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: example
+ namespace: testing
+ spec:
+ paused: True
+ wait: yes
+ wait_condition:
+ type: Progressing
+ status: Unknown
+ reason: DeploymentPaused
+
+# Patch existing namespace : add label
+- name: add label to existing namespace
+ kubernetes.core.k8s:
+ state: patched
+ kind: Namespace
+ name: patch_namespace
+ definition:
+ metadata:
+ labels:
+ support: patch
+
+# Create object using generateName
+- name: create resource using name generated by the server
+ kubernetes.core.k8s:
+ state: present
+ generate_name: pod-
+ definition:
+ apiVersion: v1
+ kind: Pod
+ spec:
+ containers:
+ - name: py
+ image: python:3.7-alpine
+ imagePullPolicy: IfNotPresent
+
+# Server side apply
+- name: Create configmap using server side apply
+ kubernetes.core.k8s:
+ namespace: testing
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: my-configmap
+ apply: yes
+ server_side_apply:
+ field_manager: ansible
+"""
+
+RETURN = r"""
+result:
+ description:
+ - The created, patched, or otherwise present object. Will be empty in the case of a deletion.
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: Represents the REST resource this object represents.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: complex
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: complex
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: complex
+ items:
+ description: Returned only when multiple yaml documents are passed to src or resource_definition
+ returned: when resource_definition or src contains list of objects
+ type: list
+ duration:
+ description: elapsed time of task in seconds
+ returned: when C(wait) is true
+ type: int
+ sample: 48
+ error:
+ description: error while trying to create/delete the object.
+ returned: error
+ type: complex
+"""
+
+import copy
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+ WAIT_ARG_SPEC,
+ NAME_ARG_SPEC,
+ RESOURCE_ARG_SPEC,
+ DELETE_OPTS_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
+ run_module,
+)
+
+
+def validate_spec():
+ return dict(
+ fail_on_error=dict(type="bool"),
+ version=dict(),
+ strict=dict(type="bool", default=True),
+ )
+
+
+def server_apply_spec():
+ return dict(
+ field_manager=dict(type="str", required=True),
+ force_conflicts=dict(type="bool", default=False),
+ )
+
+
+def argspec():
+ argument_spec = copy.deepcopy(NAME_ARG_SPEC)
+ argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
+ argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
+ argument_spec.update(copy.deepcopy(WAIT_ARG_SPEC))
+ argument_spec["merge_type"] = dict(
+ type="list", elements="str", choices=["json", "merge", "strategic-merge"]
+ )
+ argument_spec["validate"] = dict(type="dict", default=None, options=validate_spec())
+ argument_spec["append_hash"] = dict(type="bool", default=False)
+ argument_spec["apply"] = dict(type="bool", default=False)
+ argument_spec["template"] = dict(type="raw", default=None)
+ argument_spec["delete_options"] = dict(
+ type="dict", default=None, options=copy.deepcopy(DELETE_OPTS_ARG_SPEC)
+ )
+ argument_spec["continue_on_error"] = dict(type="bool", default=False)
+ argument_spec["state"] = dict(
+ default="present", choices=["present", "absent", "patched"]
+ )
+ argument_spec["force"] = dict(type="bool", default=False)
+ argument_spec["label_selectors"] = dict(type="list", elements="str")
+ argument_spec["generate_name"] = dict()
+ argument_spec["server_side_apply"] = dict(
+ type="dict", default=None, options=server_apply_spec()
+ )
+
+ return argument_spec
+
+
+def main():
+ mutually_exclusive = [
+ ("resource_definition", "src"),
+ ("merge_type", "apply"),
+ ("template", "resource_definition"),
+ ("template", "src"),
+ ("name", "generate_name"),
+ ]
+
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+ try:
+ run_module(module)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_cluster_info.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_cluster_info.py
new file mode 100644
index 00000000..9cd2ac17
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_cluster_info.py
@@ -0,0 +1,232 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Abhijeet Kasurde
+# 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_cluster_info
+
+version_added: "0.11.1"
+
+short_description: Describe Kubernetes (K8s) cluster, APIs available and their respective versions
+
+author:
+ - Abhijeet Kasurde (@Akasurde)
+
+description:
+ - Use the Kubernetes Python client to perform read operations on K8s objects.
+ - Authenticate using either a config file, certificates, password or token.
+ - Supports check mode.
+
+options:
+ invalidate_cache:
+ description:
+ - Invalidate cache before retrieving information about cluster.
+ type: bool
+ default: True
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = r"""
+- name: Get Cluster information
+ kubernetes.core.k8s_cluster_info:
+ register: api_status
+
+- name: Do not invalidate cache before getting information
+ kubernetes.core.k8s_cluster_info:
+ invalidate_cache: False
+ register: api_status
+"""
+
+RETURN = r"""
+connection:
+ description:
+ - Connection information
+ returned: success
+ type: dict
+ contains:
+ cert_file:
+ description:
+ - Path to client certificate.
+ type: str
+ returned: success
+ host:
+ description:
+ - Host URL
+ type: str
+ returned: success
+ password:
+ description:
+ - User password
+ type: str
+ returned: success
+ proxy:
+ description:
+ - Proxy details
+ type: str
+ returned: success
+ ssl_ca_cert:
+ description:
+ - Path to CA certificate
+ type: str
+ returned: success
+ username:
+ description:
+ - Username
+ type: str
+ returned: success
+ verify_ssl:
+ description:
+ - SSL verification status
+ type: bool
+ returned: success
+version:
+ description:
+ - Information about server and client version
+ returned: success
+ type: dict
+ contains:
+ server:
+ description: Server version
+ returned: success
+ type: dict
+ client:
+ description: Client version
+ returned: success
+ type: str
+apis:
+ description:
+ - dictionary of group + version of resource found from cluster
+ returned: success
+ type: dict
+ elements: dict
+ contains:
+ categories:
+ description: API categories
+ returned: success
+ type: list
+ name:
+ description: Resource short name
+ returned: success
+ type: str
+ namespaced:
+ description: If resource is namespaced
+ returned: success
+ type: bool
+ preferred:
+ description: If resource version preferred
+ returned: success
+ type: bool
+ short_names:
+ description: Resource short names
+ returned: success
+ type: str
+ singular_name:
+ description: Resource singular name
+ returned: success
+ type: str
+"""
+
+
+import copy
+from collections import defaultdict
+
+try:
+ from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
+ ResourceList,
+ )
+except ImportError:
+ # Handled during module setup
+ pass
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+)
+
+
+def execute_module(module, client):
+ if module.params.get("invalidate_cache"):
+ client.resources.invalidate_cache()
+ results = defaultdict(dict)
+ for resource in list(client.resources):
+ resource = resource[0]
+ if isinstance(resource, ResourceList):
+ continue
+ key = (
+ resource.group_version
+ if resource.group == ""
+ else "/".join([resource.group, resource.group_version.split("/")[-1]])
+ )
+ results[key][resource.kind] = {
+ "categories": resource.categories if resource.categories else [],
+ "name": resource.name,
+ "namespaced": resource.namespaced,
+ "preferred": resource.preferred,
+ "short_names": resource.short_names if resource.short_names else [],
+ "singular_name": resource.singular_name,
+ }
+ configuration = client.configuration
+ connection = {
+ "cert_file": configuration.cert_file,
+ "host": configuration.host,
+ "password": configuration.password,
+ "proxy": configuration.proxy,
+ "ssl_ca_cert": configuration.ssl_ca_cert,
+ "username": configuration.username,
+ "verify_ssl": configuration.verify_ssl,
+ }
+ from kubernetes import __version__ as version
+
+ version_info = {
+ "client": version,
+ "server": client.client.version,
+ }
+ module.exit_json(
+ changed=False, apis=results, connection=connection, version=version_info
+ )
+
+
+def argspec():
+ spec = copy.deepcopy(AUTH_ARG_SPEC)
+ spec["invalidate_cache"] = dict(type="bool", default=True)
+ return spec
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
+ )
+
+ from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+ )
+
+ try:
+ execute_module(module, client=get_api_client(module=module))
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_cp.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_cp.py
new file mode 100644
index 00000000..e8f1dea7
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_cp.py
@@ -0,0 +1,225 @@
+#!/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 content into a file in the remote pod
+ kubernetes.core.k8s_cp:
+ state: to_pod
+ namespace: some-namespace
+ pod: some-pod
+ remote_path: /tmp/foo.txt
+ content: "This content will be copied into remote file"
+"""
+
+
+RETURN = r"""
+result:
+ description:
+ - message describing the copy operation successfully done.
+ returned: success
+ type: str
+"""
+
+import copy
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+)
+
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.copy import (
+ K8SCopyFromPod,
+ K8SCopyToPod,
+ check_pod,
+)
+from ansible.module_utils._text import to_native
+
+
+def argspec():
+ argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
+ argument_spec["namespace"] = {"type": "str", "required": True}
+ argument_spec["pod"] = {"type": "str", "required": True}
+ argument_spec["container"] = {}
+ argument_spec["remote_path"] = {"type": "path", "required": True}
+ argument_spec["local_path"] = {"type": "path"}
+ argument_spec["content"] = {"type": "str"}
+ argument_spec["state"] = {
+ "type": "str",
+ "default": "to_pod",
+ "choices": ["to_pod", "from_pod"],
+ }
+ argument_spec["no_preserve"] = {"type": "bool", "default": False}
+ return argument_spec
+
+
+def execute_module(module):
+ client = get_api_client(module=module)
+ svc = K8sService(client, module)
+ containers = check_pod(svc)
+ if len(containers) > 1 and module.params.get("container") is None:
+ module.fail_json(
+ msg="Pod contains more than 1 container, option 'container' should be set"
+ )
+
+ state = module.params.get("state")
+ if state == "to_pod":
+ k8s_copy = K8SCopyToPod(module, client.client)
+ else:
+ k8s_copy = K8SCopyFromPod(module, client.client)
+
+ try:
+ k8s_copy.run()
+ except Exception as e:
+ module.fail_json("Failed to copy object due to: {0}".format(to_native(e)))
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ check_pyyaml=False,
+ mutually_exclusive=[("local_path", "content")],
+ required_if=[("state", "from_pod", ["local_path"])],
+ required_one_of=[["local_path", "content"]],
+ supports_check_mode=True,
+ )
+
+ try:
+ execute_module(module)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_drain.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_drain.py
new file mode 100644
index 00000000..31596d8c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_drain.py
@@ -0,0 +1,513 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2021, Aubin Bikouo <@abikouo>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+
+module: k8s_drain
+
+short_description: Drain, Cordon, or Uncordon node in k8s cluster
+
+version_added: "2.2.0"
+
+author: Aubin Bikouo (@abikouo)
+
+description:
+ - Drain node in preparation for maintenance same as kubectl drain.
+ - Cordon will mark the node as unschedulable.
+ - Uncordon will mark the node as schedulable.
+ - The given node will be marked unschedulable to prevent new pods from arriving.
+ - Then drain deletes all pods except mirror pods (which cannot be deleted through the API server).
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+
+options:
+ state:
+ description:
+ - Determines whether to drain, cordon, or uncordon node.
+ type: str
+ default: drain
+ choices: [ cordon, drain, uncordon ]
+ name:
+ description:
+ - The name of the node.
+ required: true
+ type: str
+ delete_options:
+ type: dict
+ description:
+ - Specify options to delete pods.
+ - This option has effect only when C(state) is set to I(drain).
+ suboptions:
+ terminate_grace_period:
+ description:
+ - Specify how many seconds to wait before forcefully terminating.
+ - If not specified, the default grace period for the object type will be used.
+ - The value zero indicates delete immediately.
+ required: false
+ type: int
+ force:
+ description:
+ - Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet.
+ type: bool
+ default: False
+ ignore_daemonsets:
+ description:
+ - Ignore DaemonSet-managed pods.
+ type: bool
+ default: False
+ delete_emptydir_data:
+ description:
+ - Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).
+ type: bool
+ default: False
+ version_added: 2.3.0
+ disable_eviction:
+ description:
+ - Forces drain to use delete rather than evict.
+ type: bool
+ default: False
+ wait_timeout:
+ description:
+ - The length of time to wait in seconds for pod to be deleted before giving up, zero means infinite.
+ type: int
+ wait_sleep:
+ description:
+ - Number of seconds to sleep between checks.
+ - Ignored if C(wait_timeout) is not set.
+ default: 5
+ type: int
+
+requirements:
+ - python >= 3.6
+ - kubernetes >= 12.0.0
+"""
+
+EXAMPLES = r"""
+- name: Drain node "foo", even if there are pods not managed by a ReplicationController, Job, or DaemonSet on it.
+ kubernetes.core.k8s_drain:
+ state: drain
+ name: foo
+ force: yes
+
+- name: Drain node "foo", but abort if there are pods not managed by a ReplicationController, Job, or DaemonSet, and use a grace period of 15 minutes.
+ kubernetes.core.k8s_drain:
+ state: drain
+ name: foo
+ delete_options:
+ terminate_grace_period: 900
+
+- name: Mark node "foo" as schedulable.
+ kubernetes.core.k8s_drain:
+ state: uncordon
+ name: foo
+
+- name: Mark node "foo" as unschedulable.
+ kubernetes.core.k8s_drain:
+ state: cordon
+ name: foo
+
+"""
+
+RETURN = r"""
+result:
+ description:
+ - The node status and the number of pods deleted.
+ returned: success
+ type: str
+"""
+
+import copy
+import time
+import traceback
+
+from datetime import datetime
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+from ansible.module_utils._text import to_native
+
+try:
+ from kubernetes.client.api import core_v1_api
+ from kubernetes.client.models import V1DeleteOptions, V1ObjectMeta
+ from kubernetes.client.exceptions import ApiException
+except ImportError:
+ # ImportError are managed by the common module already.
+ pass
+
+HAS_EVICTION_API = True
+k8s_import_exception = None
+K8S_IMP_ERR = None
+
+try:
+ from kubernetes.client.models import V1beta1Eviction as v1_eviction
+except ImportError:
+ try:
+ from kubernetes.client.models import V1Eviction as v1_eviction
+ except ImportError as e:
+ k8s_import_exception = e
+ K8S_IMP_ERR = traceback.format_exc()
+ HAS_EVICTION_API = False
+
+
+def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
+ k8s_kind_mirror = "kubernetes.io/config.mirror"
+ daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], []
+ for pod in pods:
+ # check mirror pod: cannot be delete using API Server
+ if pod.metadata.annotations and k8s_kind_mirror in pod.metadata.annotations:
+ mirror.append((pod.metadata.namespace, pod.metadata.name))
+ continue
+
+ # Any finished pod can be deleted
+ if pod.status.phase in ("Succeeded", "Failed"):
+ to_delete.append((pod.metadata.namespace, pod.metadata.name))
+ continue
+
+ # Pod with local storage cannot be deleted
+ if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes):
+ localStorage.append((pod.metadata.namespace, pod.metadata.name))
+ continue
+
+ # Check replicated Pod
+ owner_ref = pod.metadata.owner_references
+ if not owner_ref:
+ unmanaged.append((pod.metadata.namespace, pod.metadata.name))
+ else:
+ for owner in owner_ref:
+ if owner.kind == "DaemonSet":
+ daemonSet.append((pod.metadata.namespace, pod.metadata.name))
+ else:
+ to_delete.append((pod.metadata.namespace, pod.metadata.name))
+
+ warnings, errors = [], []
+ if unmanaged:
+ pod_names = ",".join([pod[0] + "/" + pod[1] for pod in unmanaged])
+ if not force:
+ errors.append(
+ "cannot delete Pods not managed by ReplicationController, ReplicaSet, Job,"
+ " DaemonSet or StatefulSet (use option force set to yes): {0}.".format(
+ pod_names
+ )
+ )
+ else:
+ # Pod not managed will be deleted as 'force' is true
+ warnings.append(
+ "Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
+ pod_names
+ )
+ )
+ to_delete += unmanaged
+
+ # mirror pods warning
+ if mirror:
+ pod_names = ",".join([pod[0] + "/" + pod[1] for pod in mirror])
+ warnings.append(
+ "cannot delete mirror Pods using API server: {0}.".format(pod_names)
+ )
+
+ # local storage
+ if localStorage:
+ pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage])
+ if not delete_emptydir_data:
+ errors.append(
+ "cannot delete Pods with local storage: {0}.".format(pod_names)
+ )
+ else:
+ warnings.append("Deleting Pods with local storage: {0}.".format(pod_names))
+ for pod in localStorage:
+ to_delete.append((pod[0], pod[1]))
+
+ # DaemonSet managed Pods
+ if daemonSet:
+ pod_names = ",".join([pod[0] + "/" + pod[1] for pod in daemonSet])
+ if not ignore_daemonset:
+ errors.append(
+ "cannot delete DaemonSet-managed Pods (use option ignore_daemonset set to yes): {0}.".format(
+ pod_names
+ )
+ )
+ else:
+ warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
+ return to_delete, warnings, errors
+
+
+class K8sDrainAnsible(object):
+ def __init__(self, module, client):
+ self._module = module
+ self._api_instance = core_v1_api.CoreV1Api(client.client)
+
+ # 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 = V1DeleteOptions(
+ grace_period_seconds=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:
+ try:
+ if self._drain_options.get("disable_eviction"):
+ self._api_instance.delete_namespaced_pod(
+ name=name, namespace=namespace, body=self._delete_options
+ )
+ else:
+ body = v1_eviction(
+ delete_options=self._delete_options,
+ metadata=V1ObjectMeta(name=name, namespace=namespace),
+ )
+ self._api_instance.create_namespaced_pod_eviction(
+ name=name, namespace=namespace, body=body
+ )
+ self._changed = True
+ except ApiException as exc:
+ if exc.reason != "Not Found":
+ self._module.fail_json(
+ msg="Failed to delete pod {0}/{1} due to: {2}".format(
+ namespace, name, exc.reason
+ )
+ )
+ except Exception as exc:
+ self._module.fail_json(
+ msg="Failed to delete pod {0}/{1} due to: {2}".format(
+ namespace, name, to_native(exc)
+ )
+ )
+
+ def delete_or_evict_pods(self, node_unschedulable):
+ # Mark node as unschedulable
+ result = []
+ if not node_unschedulable:
+ self.patch_node(unschedulable=True)
+ result.append(
+ "node {0} marked unschedulable.".format(self._module.params.get("name"))
+ )
+ self._changed = True
+ else:
+ result.append(
+ "node {0} already marked unschedulable.".format(
+ self._module.params.get("name")
+ )
+ )
+
+ def _revert_node_patch():
+ if self._changed:
+ self._changed = False
+ self.patch_node(unschedulable=False)
+
+ try:
+ field_selector = "spec.nodeName={name}".format(
+ name=self._module.params.get("name")
+ )
+ pod_list = self._api_instance.list_pod_for_all_namespaces(
+ field_selector=field_selector
+ )
+ # Filter pods
+ force = self._drain_options.get("force", False)
+ ignore_daemonset = self._drain_options.get("ignore_daemonsets", False)
+ delete_emptydir_data = self._drain_options.get(
+ "delete_emptydir_data", False
+ )
+ pods, warnings, errors = filter_pods(
+ pod_list.items, force, ignore_daemonset, delete_emptydir_data
+ )
+ if errors:
+ _revert_node_patch()
+ self._module.fail_json(
+ msg="Pod deletion errors: {0}".format(" ".join(errors))
+ )
+ except ApiException as exc:
+ if exc.reason != "Not Found":
+ _revert_node_patch()
+ self._module.fail_json(
+ msg="Failed to list pod from node {name} due to: {reason}".format(
+ name=self._module.params.get("name"), reason=exc.reason
+ ),
+ status=exc.status,
+ )
+ pods = []
+ except Exception as exc:
+ _revert_node_patch()
+ self._module.fail_json(
+ msg="Failed to list pod from node {name} due to: {error}".format(
+ name=self._module.params.get("name"), error=to_native(exc)
+ )
+ )
+
+ # Delete Pods
+ if pods:
+ self.evict_pods(pods)
+ number_pod = len(pods)
+ if self._drain_options.get("wait_timeout") is not None:
+ warn = self.wait_for_pod_deletion(
+ pods,
+ self._drain_options.get("wait_timeout"),
+ self._drain_options.get("wait_sleep"),
+ )
+ if warn:
+ warnings.append(warn)
+ result.append("{0} Pod(s) deleted from node.".format(number_pod))
+ if warnings:
+ return dict(result=" ".join(result), warnings=warnings)
+ return dict(result=" ".join(result))
+
+ def patch_node(self, unschedulable):
+
+ body = {"spec": {"unschedulable": unschedulable}}
+ try:
+ self._api_instance.patch_node(
+ name=self._module.params.get("name"), body=body
+ )
+ except Exception as exc:
+ self._module.fail_json(
+ msg="Failed to patch node due to: {0}".format(to_native(exc))
+ )
+
+ def execute_module(self):
+
+ state = self._module.params.get("state")
+ name = self._module.params.get("name")
+ try:
+ node = self._api_instance.read_node(name=name)
+ except ApiException as exc:
+ if exc.reason == "Not Found":
+ self._module.fail_json(msg="Node {0} not found.".format(name))
+ self._module.fail_json(
+ msg="Failed to retrieve node '{0}' due to: {1}".format(
+ name, exc.reason
+ ),
+ status=exc.status,
+ )
+ except Exception as exc:
+ self._module.fail_json(
+ msg="Failed to retrieve node '{0}' due to: {1}".format(
+ name, to_native(exc)
+ )
+ )
+
+ result = {}
+ if state == "cordon":
+ if node.spec.unschedulable:
+ self._module.exit_json(
+ result="node {0} already marked unschedulable.".format(name)
+ )
+ self.patch_node(unschedulable=True)
+ result["result"] = "node {0} marked unschedulable.".format(name)
+ self._changed = True
+
+ elif state == "uncordon":
+ if not node.spec.unschedulable:
+ self._module.exit_json(
+ result="node {0} already marked schedulable.".format(name)
+ )
+ self.patch_node(unschedulable=False)
+ result["result"] = "node {0} marked schedulable.".format(name)
+ self._changed = True
+
+ else:
+ # drain node
+ # Delete or Evict Pods
+ ret = self.delete_or_evict_pods(node_unschedulable=node.spec.unschedulable)
+ result.update(ret)
+
+ self._module.exit_json(changed=self._changed, **result)
+
+
+def argspec():
+ argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
+ argument_spec.update(
+ dict(
+ state=dict(default="drain", choices=["cordon", "drain", "uncordon"]),
+ name=dict(required=True),
+ delete_options=dict(
+ type="dict",
+ default={},
+ options=dict(
+ terminate_grace_period=dict(type="int"),
+ force=dict(type="bool", default=False),
+ ignore_daemonsets=dict(type="bool", default=False),
+ delete_emptydir_data=dict(type="bool", default=False),
+ disable_eviction=dict(type="bool", default=False),
+ wait_timeout=dict(type="int"),
+ wait_sleep=dict(type="int", default=5),
+ ),
+ ),
+ )
+ )
+ return argument_spec
+
+
+def main():
+ module = AnsibleK8SModule(module_class=AnsibleModule, argument_spec=argspec())
+
+ if not HAS_EVICTION_API:
+ module.fail_json(
+ msg="The kubernetes Python library missing with V1Eviction API",
+ exception=K8S_IMP_ERR,
+ error=to_native(k8s_import_exception),
+ )
+
+ try:
+ client = get_api_client(module=module)
+ k8s_drain = K8sDrainAnsible(module, client.client)
+ k8s_drain.execute_module()
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_exec.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_exec.py
new file mode 100644
index 00000000..c54c23c0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_exec.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020, Red Hat
+# 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_exec
+
+short_description: Execute command in Pod
+
+version_added: "0.10.0"
+
+author: "Tristan de Cacqueray (@tristanC)"
+
+description:
+ - Use the Kubernetes Python client to execute command on K8s pods.
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "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.
+
+options:
+ proxy:
+ description:
+ - The URL of an HTTP proxy to use for the connection.
+ - Can also be specified via I(K8S_AUTH_PROXY) environment variable.
+ - Please note that this module does not pick up typical proxy settings from the environment (for example, HTTP_PROXY).
+ type: str
+ 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 connect to.
+ - Defaults to only container if there is only one container in the pod.
+ - If not specified, will choose the first container from the given pod as kubectl cmdline does.
+ type: str
+ required: no
+ command:
+ description:
+ - The command to execute.
+ type: str
+ required: yes
+"""
+
+EXAMPLES = r"""
+- name: Execute a command
+ kubernetes.core.k8s_exec:
+ namespace: myproject
+ pod: zuul-scheduler
+ command: zuul-scheduler full-reconfigure
+
+- name: Check RC status of command executed
+ kubernetes.core.k8s_exec:
+ namespace: myproject
+ pod: busybox-test
+ command: cmd_with_non_zero_exit_code
+ register: command_status
+ ignore_errors: True
+
+- name: Check last command status
+ debug:
+ msg: "cmd failed"
+ when: command_status.rc != 0
+
+- name: Specify a container name to execute the command on
+ kubernetes.core.k8s_exec:
+ namespace: myproject
+ pod: busybox-test
+ container: manager
+ command: echo "hello"
+"""
+
+RETURN = r"""
+result:
+ description:
+ - The command object
+ returned: success
+ type: complex
+ contains:
+ stdout:
+ description: The command stdout
+ type: str
+ stdout_lines:
+ description: The command stdout
+ type: str
+ stderr:
+ description: The command stderr
+ type: str
+ stderr_lines:
+ description: The command stderr
+ type: str
+ 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
+import shlex
+
+try:
+ import yaml
+except ImportError:
+ # ImportError are managed by the common module already.
+ pass
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible.module_utils._text import to_native
+from ansible_collections.kubernetes.core.plugins.module_utils.common import (
+ AUTH_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+try:
+ from kubernetes.client.apis import core_v1_api
+ from kubernetes.stream import stream
+ from kubernetes.client.exceptions import ApiException
+except ImportError:
+ # ImportError are managed by the common module already.
+ pass
+
+
+def argspec():
+ spec = copy.deepcopy(AUTH_ARG_SPEC)
+ spec["namespace"] = dict(type="str", required=True)
+ spec["pod"] = dict(type="str", required=True)
+ spec["container"] = dict(type="str")
+ spec["command"] = dict(type="str", required=True)
+ return spec
+
+
+def execute_module(module, client):
+ # Load kubernetes.client.Configuration
+ api = core_v1_api.CoreV1Api(client.client)
+
+ # hack because passing the container as None breaks things
+ optional_kwargs = {}
+ if module.params.get("container"):
+ optional_kwargs["container"] = module.params["container"]
+ else:
+ # default to the first container available on pod
+ resp = None
+ try:
+ resp = api.read_namespaced_pod(
+ name=module.params["pod"], namespace=module.params["namespace"]
+ )
+ except ApiException:
+ pass
+
+ if resp and len(resp.spec.containers) >= 1:
+ optional_kwargs["container"] = resp.spec.containers[0].name
+
+ try:
+ resp = stream(
+ api.connect_get_namespaced_pod_exec,
+ module.params["pod"],
+ module.params["namespace"],
+ command=shlex.split(module.params["command"]),
+ stdout=True,
+ stderr=True,
+ stdin=False,
+ tty=False,
+ _preload_content=False,
+ **optional_kwargs
+ )
+ except Exception as e:
+ module.fail_json(
+ msg="Failed to execute on pod %s"
+ " due to : %s" % (module.params.get("pod"), to_native(e))
+ )
+ stdout, stderr, rc = [], [], 0
+ while resp.is_open():
+ resp.update(timeout=1)
+ if resp.peek_stdout():
+ stdout.append(resp.read_stdout())
+ if resp.peek_stderr():
+ stderr.append(resp.read_stderr())
+ err = resp.read_channel(3)
+ err = yaml.safe_load(err)
+ if err["status"] == "Success":
+ rc = 0
+ else:
+ rc = int(err["details"]["causes"][0]["message"])
+
+ module.deprecate(
+ "The 'return_code' return key is being renamed to 'rc'. "
+ "Both keys are being returned for now to allow users to migrate their automation.",
+ 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,
+ )
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ check_pyyaml=False,
+ argument_spec=argspec(),
+ supports_check_mode=True,
+ )
+
+ try:
+ client = get_api_client(module)
+ execute_module(module, client.client)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_info.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_info.py
new file mode 100644
index 00000000..4b29be11
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_info.py
@@ -0,0 +1,217 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2018, Will Thames <@willthames>
+# 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_info
+
+short_description: Describe Kubernetes (K8s) objects
+
+author:
+ - "Will Thames (@willthames)"
+
+description:
+ - Use the Kubernetes Python client to perform read operations on K8s objects.
+ - Access to the full range of K8s APIs.
+ - Authenticate using either a config file, certificates, password or token.
+ - Supports check mode.
+ - This module was called C(k8s_facts) before Ansible 2.9. The usage did not change.
+
+options:
+ kind:
+ description:
+ - Use to specify an object model.
+ - Use to create, delete, or discover an object without providing a full resource definition.
+ - Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
+ - If I(resource definition) is provided, the I(kind) value from the I(resource_definition)
+ will override this option.
+ type: str
+ required: True
+ label_selectors:
+ description: List of label selectors to use to filter results
+ type: list
+ elements: str
+ field_selectors:
+ description: List of field selectors to use to filter results
+ type: list
+ elements: str
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_name_options
+ - kubernetes.core.k8s_wait_options
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = r"""
+- name: Get an existing Service object
+ kubernetes.core.k8s_info:
+ api_version: v1
+ kind: Service
+ name: web
+ namespace: testing
+ register: web_service
+
+- name: Get a list of all service objects
+ kubernetes.core.k8s_info:
+ api_version: v1
+ kind: Service
+ namespace: testing
+ register: service_list
+
+- name: Get a list of all pods from any namespace
+ kubernetes.core.k8s_info:
+ kind: Pod
+ register: pod_list
+
+- name: Search for all Pods labelled app=web
+ kubernetes.core.k8s_info:
+ kind: Pod
+ label_selectors:
+ - app = web
+ - tier in (dev, test)
+
+- name: Using vars while using label_selectors
+ kubernetes.core.k8s_info:
+ kind: Pod
+ label_selectors:
+ - "app = {{ app_label_web }}"
+ vars:
+ app_label_web: web
+
+- name: Search for all running pods
+ kubernetes.core.k8s_info:
+ kind: Pod
+ field_selectors:
+ - status.phase=Running
+
+- name: List custom objects created using CRD
+ kubernetes.core.k8s_info:
+ kind: MyCustomObject
+ api_version: "stable.example.com/v1"
+
+- name: Wait till the Object is created
+ kubernetes.core.k8s_info:
+ kind: Pod
+ wait: yes
+ name: pod-not-yet-created
+ namespace: default
+ wait_sleep: 10
+ wait_timeout: 360
+"""
+
+RETURN = r"""
+api_found:
+ description:
+ - Whether the specified api_version and kind were successfully mapped to an existing API on the targeted cluster.
+ - Version added 1.2.0.
+ returned: always
+ type: bool
+resources:
+ description:
+ - The object(s) that exists
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: Represents the REST resource this object represents.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: dict
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: dict
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: dict
+"""
+
+import copy
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+ WAIT_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+)
+
+
+def execute_module(module, svc):
+ facts = svc.find(
+ module.params["kind"],
+ module.params["api_version"],
+ name=module.params["name"],
+ namespace=module.params["namespace"],
+ label_selectors=module.params["label_selectors"],
+ field_selectors=module.params["field_selectors"],
+ wait=module.params["wait"],
+ wait_sleep=module.params["wait_sleep"],
+ wait_timeout=module.params["wait_timeout"],
+ condition=module.params["wait_condition"],
+ )
+ module.exit_json(changed=False, **facts)
+
+
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(WAIT_ARG_SPEC)
+ args.update(
+ dict(
+ kind=dict(required=True),
+ api_version=dict(default="v1", aliases=["api", "version"]),
+ name=dict(),
+ namespace=dict(),
+ label_selectors=dict(type="list", elements="str", default=[]),
+ field_selectors=dict(type="list", elements="str", default=[]),
+ )
+ )
+ return args
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
+ )
+ try:
+ client = get_api_client(module)
+ svc = K8sService(client, module)
+ execute_module(module, svc)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_json_patch.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_json_patch.py
new file mode 100644
index 00000000..5ea8dbc9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_json_patch.py
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (C), 2018 Red Hat | Ansible
+# 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_json_patch
+
+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(kubernetes.core.k8s) module for strategic merge or JSON merge operations.
+ - The jsonpatch library is required for check mode.
+
+version_added: 2.0.0
+
+author:
+- Mike Graves (@gravesm)
+
+options:
+ api_version:
+ description:
+ - Use to specify the API version.
+ - Use in conjunction with I(kind), I(name), and I(namespace) to identify a specific object.
+ type: str
+ default: v1
+ aliases:
+ - api
+ - version
+ kind:
+ description:
+ - Use to specify an object model.
+ - Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
+ type: str
+ required: yes
+ namespace:
+ description:
+ - Use to specify an object namespace.
+ - Use in conjunction with I(api_version), I(kind), and I(name) to identify a specific object.
+ type: str
+ name:
+ description:
+ - Use to specify an object name.
+ - Use in conjunction with I(api_version), I(kind), and I(namespace) to identify a specific object.
+ type: str
+ required: yes
+ patch:
+ description:
+ - List of JSON patch operations.
+ required: yes
+ type: list
+ elements: dict
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_wait_options
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+ - "jsonpatch"
+"""
+
+EXAMPLES = r"""
+- name: Apply multiple patch operations to an existing Pod
+ kubernetes.core.k8s_json_patch:
+ kind: Pod
+ namespace: testing
+ name: mypod
+ patch:
+ - op: add
+ path: /metadata/labels/app
+ value: myapp
+ - op: replace
+ path: /spec/containers/0/image
+ value: nginx
+"""
+
+RETURN = r"""
+result:
+ description: The modified object.
+ returned: success
+ type: dict
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: The REST resource this object represents.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: dict
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: dict
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: dict
+duration:
+ description: Elapsed time of task in seconds.
+ returned: when C(wait) is true
+ type: int
+ sample: 48
+error:
+ description: The error when patching the object.
+ returned: error
+ type: dict
+ sample: {
+ "msg": "Failed to import the required Python library (jsonpatch) ...",
+ "exception": "Traceback (most recent call last): ..."
+ }
+"""
+
+import copy
+import traceback
+
+from ansible.module_utils.basic import missing_required_lib
+from ansible.module_utils._text import to_native
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+ WAIT_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ diff_objects,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
+ get_waiter,
+)
+
+
+try:
+ from kubernetes.dynamic.exceptions import DynamicApiError
+except ImportError:
+ # kubernetes library check happens in common.py
+ pass
+
+JSON_PATCH_IMPORT_ERR = None
+try:
+ import jsonpatch
+
+ HAS_JSON_PATCH = True
+except ImportError:
+ HAS_JSON_PATCH = False
+ JSON_PATCH_IMPORT_ERR = traceback.format_exc()
+
+
+JSON_PATCH_ARGS = {
+ "api_version": {"default": "v1", "aliases": ["api", "version"]},
+ "kind": {"type": "str", "required": True},
+ "namespace": {"type": "str"},
+ "name": {"type": "str", "required": True},
+ "patch": {"type": "list", "required": True, "elements": "dict"},
+}
+
+
+def json_patch(existing, patch):
+ if not HAS_JSON_PATCH:
+ error = {
+ "msg": missing_required_lib("jsonpatch"),
+ "exception": JSON_PATCH_IMPORT_ERR,
+ }
+ return None, error
+ try:
+ patch = jsonpatch.JsonPatch(patch)
+ patched = patch.apply(existing)
+ return patched, None
+ except jsonpatch.InvalidJsonPatch as e:
+ error = {"msg": "Invalid JSON patch", "exception": e}
+ return None, error
+ except jsonpatch.JsonPatchConflict as e:
+ error = {"msg": "Patch could not be applied due to a conflict", "exception": e}
+ return None, error
+
+
+def execute_module(module, client):
+ kind = module.params.get("kind")
+ api_version = module.params.get("api_version")
+ name = module.params.get("name")
+ namespace = module.params.get("namespace")
+ patch = module.params.get("patch")
+
+ wait = module.params.get("wait")
+ wait_sleep = module.params.get("wait_sleep")
+ wait_timeout = module.params.get("wait_timeout")
+ wait_condition = None
+ if module.params.get("wait_condition") and module.params.get("wait_condition").get(
+ "type"
+ ):
+ wait_condition = module.params["wait_condition"]
+
+ def build_error_msg(kind, name, msg):
+ return "%s %s: %s" % (kind, name, msg)
+
+ resource = client.resource(kind, api_version)
+
+ try:
+ existing = client.get(resource, name=name, namespace=namespace)
+ except DynamicApiError as exc:
+ msg = "Failed to retrieve requested object: {0}".format(exc.body)
+ module.fail_json(
+ msg=build_error_msg(kind, name, msg),
+ error=exc.status,
+ status=exc.status,
+ reason=exc.reason,
+ )
+ except ValueError as exc:
+ msg = "Failed to retrieve requested object: {0}".format(to_native(exc))
+ module.fail_json(
+ msg=build_error_msg(kind, name, msg), error="", status="", reason=""
+ )
+
+ if module.check_mode and not client.dry_run:
+ obj, error = json_patch(existing.to_dict(), patch)
+ if error:
+ module.fail_json(**error)
+ else:
+ params = {}
+ if module.check_mode:
+ params["dry_run"] = "All"
+ try:
+ obj = client.patch(
+ resource,
+ patch,
+ name=name,
+ namespace=namespace,
+ content_type="application/json-patch+json",
+ **params
+ ).to_dict()
+ except DynamicApiError as exc:
+ msg = "Failed to patch existing object: {0}".format(exc.body)
+ module.fail_json(
+ msg=msg, error=exc.status, status=exc.status, reason=exc.reason
+ )
+ except Exception as exc:
+ msg = "Failed to patch existing object: {0}".format(exc)
+ module.fail_json(msg=msg, error=to_native(exc), status="", reason="")
+
+ success = True
+ result = {"result": obj}
+ if wait and not module.check_mode:
+ waiter = get_waiter(client, resource, condition=wait_condition)
+ success, result["result"], result["duration"] = waiter.wait(
+ wait_timeout, wait_sleep, name, namespace
+ )
+ match, diffs = diff_objects(existing.to_dict(), obj)
+ result["changed"] = not match
+ if module._diff:
+ result["diff"] = diffs
+
+ if not success:
+ msg = "Resource update timed out"
+ module.fail_json(msg=msg, **result)
+
+ module.exit_json(**result)
+
+
+def main():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(copy.deepcopy(WAIT_ARG_SPEC))
+ args.update(JSON_PATCH_ARGS)
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule, argument_spec=args, supports_check_mode=True
+ )
+ try:
+ client = get_api_client(module)
+ execute_module(module, client)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_log.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_log.py
new file mode 100644
index 00000000..e52d5bce
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_log.py
@@ -0,0 +1,362 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2019, Fabian von Feilitzsch <@fabianvf>
+# 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_log
+
+short_description: Fetch logs from Kubernetes resources
+
+version_added: "0.10.0"
+
+author:
+ - "Fabian von Feilitzsch (@fabianvf)"
+
+description:
+ - Use the Kubernetes Python client to perform read operations on K8s log endpoints.
+ - Authenticate using either a config file, certificates, password or token.
+ - Supports check mode.
+ - Analogous to `kubectl logs` or `oc logs`
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_name_options
+options:
+ kind:
+ description:
+ - Use to specify an object model.
+ - Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
+ - If using I(label_selectors), cannot be overridden.
+ type: str
+ default: Pod
+ name:
+ description:
+ - Use to specify an object name.
+ - Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a specific object.
+ - Only one of I(name) or I(label_selectors) may be provided.
+ type: str
+ label_selectors:
+ description:
+ - List of label selectors to use to filter results
+ - Only one of I(name) or I(label_selectors) may be provided.
+ type: list
+ elements: str
+ container:
+ description:
+ - Use to specify the container within a pod to grab the log from.
+ - If there is only one container, this will default to that container.
+ - If there is more than one container, this option is required or set I(all_containers) to C(true).
+ - mutually exclusive with C(all_containers).
+ 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'
+ previous:
+ description:
+ - If C(true), print the logs for the previous instance of the container in a pod if it exists.
+ required: no
+ type: bool
+ default: False
+ version_added: '2.4.0'
+ tail_lines:
+ description:
+ - A number of lines from the end of the logs to retrieve.
+ required: no
+ type: int
+ version_added: '2.4.0'
+ all_containers:
+ description:
+ - If set to C(true), retrieve all containers' logs in the pod(s).
+ - mutually exclusive with C(container).
+ type: bool
+ version_added: '2.4.0'
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = r"""
+- name: Get a log from a Pod
+ kubernetes.core.k8s_log:
+ name: example-1
+ namespace: testing
+ register: log
+
+# This will get the log from the first Pod found matching the selector
+- name: Log a Pod matching a label selector
+ kubernetes.core.k8s_log:
+ namespace: testing
+ label_selectors:
+ - app=example
+ register: log
+
+# This will get the log from a single Pod managed by this Deployment
+- name: Get a log from a Deployment
+ kubernetes.core.k8s_log:
+ api_version: apps/v1
+ kind: Deployment
+ namespace: testing
+ name: example
+ since_seconds: "4000"
+ register: log
+
+# This will get the log from a single Pod managed by this DeploymentConfig
+- name: Get a log from a DeploymentConfig
+ kubernetes.core.k8s_log:
+ api_version: apps.openshift.io/v1
+ kind: DeploymentConfig
+ namespace: testing
+ name: example
+ tail_lines: 100
+ register: log
+
+# This will get the logs from all containers in Pod
+- name: Get the logs from all containers in pod
+ kubernetes.core.k8s_log:
+ namespace: testing
+ name: some-pod
+ all_containers: true
+"""
+
+RETURN = r"""
+log:
+ type: str
+ description:
+ - The text log of the object
+ returned: success
+log_lines:
+ type: list
+ description:
+ - The log of the object, split on newlines
+ returned: success
+"""
+
+
+import copy
+import json
+
+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,
+ NAME_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+)
+
+try:
+ from kubernetes.client.exceptions import ApiException
+except ImportError:
+ # ImportError are managed by the common module already.
+ pass
+
+
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(
+ dict(
+ kind=dict(type="str", default="Pod"),
+ container=dict(),
+ since_seconds=dict(),
+ label_selectors=dict(type="list", elements="str", default=[]),
+ previous=dict(type="bool", default=False),
+ tail_lines=dict(type="int"),
+ all_containers=dict(type="bool"),
+ )
+ )
+ return args
+
+
+def get_exception_message(exc):
+ try:
+ d = json.loads(exc.body.decode("utf8"))
+ return d["message"]
+ except Exception:
+ return exc
+
+
+def list_containers_in_pod(svc, resource, namespace, name):
+ try:
+ result = svc.client.get(resource, name=name, namespace=namespace)
+ containers = [
+ c["name"] for c in result.to_dict()["status"]["containerStatuses"]
+ ]
+ return containers
+ except Exception as exc:
+ raise CoreException(
+ "Unable to retrieve log from Pod due to: {0}".format(
+ get_exception_message(exc)
+ )
+ )
+
+
+def execute_module(svc, params):
+ name = params.get("name")
+ namespace = params.get("namespace")
+ label_selector = ",".join(params.get("label_selectors", {}))
+ if name and label_selector:
+ raise CoreException("Only one of name or label_selectors can be provided")
+
+ resource = svc.find_resource(params["kind"], params["api_version"], fail=True)
+ v1_pods = svc.find_resource("Pod", "v1", fail=True)
+
+ if "log" not in resource.subresources:
+ if not name:
+ raise CoreException(
+ "name must be provided for resources that do not support the log subresource"
+ )
+ instance = resource.get(name=name, namespace=namespace)
+ label_selector = ",".join(extract_selectors(instance))
+ resource = v1_pods
+
+ if label_selector:
+ instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
+ if not instances.items:
+ raise CoreException(
+ "No pods in namespace {0} matched selector {1}".format(
+ namespace, label_selector
+ )
+ )
+ # This matches the behavior of kubectl when logging pods via a selector
+ name = instances.items[0].metadata.name
+ resource = v1_pods
+
+ if "base" not in resource.log.urls and not name:
+ raise CoreException(
+ "name must be provided for resources that do not support namespaced base url"
+ )
+
+ kwargs = {}
+ if params.get("container"):
+ kwargs["query_params"] = {"container": params["container"]}
+
+ if params.get("since_seconds"):
+ kwargs.setdefault("query_params", {}).update(
+ {"sinceSeconds": params["since_seconds"]}
+ )
+
+ if params.get("previous"):
+ kwargs.setdefault("query_params", {}).update({"previous": params["previous"]})
+
+ if params.get("tail_lines"):
+ kwargs.setdefault("query_params", {}).update(
+ {"tailLines": params["tail_lines"]}
+ )
+
+ pod_containers = [None]
+ if params.get("all_containers"):
+ pod_containers = list_containers_in_pod(svc, resource, namespace, name)
+
+ log = ""
+ try:
+ for container in pod_containers:
+ if container is not None:
+ kwargs.setdefault("query_params", {}).update({"container": container})
+ response = resource.log.get(
+ name=name, namespace=namespace, serialize=False, **kwargs
+ )
+ log += response.data.decode("utf8")
+ except ApiException as exc:
+ if exc.reason == "Not Found":
+ raise CoreException("Pod {0}/{1} not found.".format(namespace, name))
+ raise CoreException(
+ "Unable to retrieve log from Pod due to: {0}".format(
+ get_exception_message(exc)
+ )
+ )
+
+ return {"changed": False, "log": log, "log_lines": log.split("\n")}
+
+
+def extract_selectors(instance):
+ # Parses selectors on an object based on the specifications documented here:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
+ selectors = []
+ if not instance.spec.selector:
+ raise CoreException(
+ "{0} {1} does not support the log subresource directly, and no Pod selector was found on the object".format(
+ "/".join(instance.group, instance.apiVersion), instance.kind
+ )
+ )
+
+ if not (
+ instance.spec.selector.matchLabels or instance.spec.selector.matchExpressions
+ ):
+ # A few resources (like DeploymentConfigs) just use a simple key:value style instead of supporting expressions
+ for k, v in dict(instance.spec.selector).items():
+ selectors.append("{0}={1}".format(k, v))
+ return selectors
+
+ if instance.spec.selector.matchLabels:
+ for k, v in dict(instance.spec.selector.matchLabels).items():
+ selectors.append("{0}={1}".format(k, v))
+
+ if instance.spec.selector.matchExpressions:
+ for expression in instance.spec.selector.matchExpressions:
+ operator = expression.operator
+
+ if operator == "Exists":
+ selectors.append(expression.key)
+ elif operator == "DoesNotExist":
+ selectors.append("!{0}".format(expression.key))
+ elif operator in ["In", "NotIn"]:
+ selectors.append(
+ "{key} {operator} {values}".format(
+ key=expression.key,
+ operator=operator.lower(),
+ values="({0})".format(", ".join(expression.values)),
+ )
+ )
+ else:
+ raise CoreException(
+ "The k8s_log module does not support the {0} matchExpression operator".format(
+ operator.lower()
+ )
+ )
+
+ return selectors
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ supports_check_mode=True,
+ mutually_exclusive=[("container", "all_containers")],
+ )
+
+ try:
+ client = get_api_client(module=module)
+ svc = K8sService(client, module)
+ result = execute_module(svc, module.params)
+ module.exit_json(**result)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_rollback.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_rollback.py
new file mode 100644
index 00000000..8dfab459
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_rollback.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Julien Huon <@julienhuon> Institut National de l'Audiovisuel
+# 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_rollback
+short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
+version_added: "1.0.0"
+author:
+ - "Julien Huon (@julienhuon)"
+description:
+ - Use the Kubernetes Python client to perform the Rollback.
+ - Authenticate using either a config file, certificates, password or token.
+ - Similar to the C(kubectl rollout undo) command.
+options:
+ label_selectors:
+ description: List of label selectors to use to filter results.
+ type: list
+ elements: str
+ field_selectors:
+ description: List of field selectors to use to filter results.
+ type: list
+ elements: str
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_name_options
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = r"""
+- name: Rollback a failed deployment
+ kubernetes.core.k8s_rollback:
+ api_version: apps/v1
+ kind: Deployment
+ name: web
+ namespace: testing
+"""
+
+RETURN = r"""
+rollback_info:
+ description:
+ - The object that was rolled back.
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ code:
+ description: The HTTP Code of the response
+ returned: success
+ type: str
+ kind:
+ description: Status
+ returned: success
+ type: str
+ metadata:
+ description:
+ - Standard object metadata.
+ - Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: dict
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: dict
+"""
+
+import copy
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+ NAME_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+)
+
+
+def get_managed_resource(kind):
+ managed_resource = {}
+
+ if kind == "DaemonSet":
+ managed_resource["kind"] = "ControllerRevision"
+ managed_resource["api_version"] = "apps/v1"
+ elif kind == "Deployment":
+ managed_resource["kind"] = "ReplicaSet"
+ managed_resource["api_version"] = "apps/v1"
+ else:
+ raise CoreException(
+ "Cannot perform rollback on resource of kind {0}".format(kind)
+ )
+ return managed_resource
+
+
+def execute_module(svc):
+ results = []
+ module = svc.module
+
+ resources = svc.find(
+ module.params["kind"],
+ module.params["api_version"],
+ module.params["name"],
+ module.params["namespace"],
+ module.params["label_selectors"],
+ module.params["field_selectors"],
+ )
+
+ changed = False
+ for resource in resources["resources"]:
+ result = perform_action(svc, resource)
+ changed = result["changed"] or changed
+ results.append(result)
+
+ module.exit_json(**{"changed": changed, "rollback_info": results})
+
+
+def perform_action(svc, resource):
+ module = svc.module
+
+ if module.params["kind"] == "DaemonSet":
+ current_revision = resource["metadata"]["generation"]
+ elif module.params["kind"] == "Deployment":
+ current_revision = resource["metadata"]["annotations"][
+ "deployment.kubernetes.io/revision"
+ ]
+
+ managed_resource = get_managed_resource(module.params["kind"])
+ managed_resources = svc.find(
+ managed_resource["kind"],
+ managed_resource["api_version"],
+ "",
+ module.params["namespace"],
+ resource["spec"]["selector"]["matchLabels"],
+ "",
+ )
+
+ prev_managed_resource = get_previous_revision(
+ managed_resources["resources"], current_revision
+ )
+ if not prev_managed_resource:
+ warn = "No rollout history found for resource %s/%s" % (
+ module.params["kind"],
+ resource["metadata"]["name"],
+ )
+ result = {"changed": False, "warnings": [warn]}
+ return result
+
+ if module.params["kind"] == "Deployment":
+ del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
+ "pod-template-hash"
+ ]
+
+ resource_patch = [
+ {
+ "op": "replace",
+ "path": "/spec/template",
+ "value": prev_managed_resource["spec"]["template"],
+ },
+ {
+ "op": "replace",
+ "path": "/metadata/annotations",
+ "value": {
+ "deployment.kubernetes.io/revision": prev_managed_resource[
+ "metadata"
+ ]["annotations"]["deployment.kubernetes.io/revision"]
+ },
+ },
+ ]
+
+ api_target = "deployments"
+ content_type = "application/json-patch+json"
+ elif module.params["kind"] == "DaemonSet":
+ resource_patch = prev_managed_resource["data"]
+
+ api_target = "daemonsets"
+ content_type = "application/strategic-merge-patch+json"
+
+ rollback = resource
+ if not module.check_mode:
+ rollback = svc.client.client.request(
+ "PATCH",
+ "/apis/{0}/namespaces/{1}/{2}/{3}".format(
+ module.params["api_version"],
+ module.params["namespace"],
+ api_target,
+ module.params["name"],
+ ),
+ body=resource_patch,
+ content_type=content_type,
+ ).to_dict()
+
+ result = {"changed": True}
+ result["method"] = "patch"
+ result["body"] = resource_patch
+ result["resources"] = rollback
+ return result
+
+
+def argspec():
+ args = copy.deepcopy(AUTH_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(
+ dict(
+ label_selectors=dict(type="list", elements="str", default=[]),
+ field_selectors=dict(type="list", elements="str", default=[]),
+ )
+ )
+ return args
+
+
+def get_previous_revision(all_resources, current_revision):
+ for resource in all_resources:
+ if resource["kind"] == "ReplicaSet":
+ if (
+ int(
+ resource["metadata"]["annotations"][
+ "deployment.kubernetes.io/revision"
+ ]
+ )
+ == int(current_revision) - 1
+ ):
+ return resource
+ elif resource["kind"] == "ControllerRevision":
+ if (
+ int(
+ resource["metadata"]["annotations"][
+ "deprecated.daemonset.template.generation"
+ ]
+ )
+ == int(current_revision) - 1
+ ):
+ return resource
+ return None
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
+ )
+
+ try:
+ client = get_api_client(module=module)
+ svc = K8sService(client, module)
+ execute_module(svc)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_scale.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_scale.py
new file mode 100644
index 00000000..a7cdfe1e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_scale.py
@@ -0,0 +1,422 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2018, Chris Houseknecht <@chouseknecht>
+# 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_scale
+
+short_description: Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
+
+author:
+ - "Chris Houseknecht (@chouseknecht)"
+ - "Fabian von Feilitzsch (@fabianvf)"
+
+description:
+ - Similar to the kubectl scale command. Use to set the number of replicas for a Deployment, ReplicaSet,
+ or Replication Controller, or the parallelism attribute of a Job. Supports check mode.
+ - C(wait) parameter is not supported for Jobs.
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_name_options
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_resource_options
+ - kubernetes.core.k8s_scale_options
+
+options:
+ label_selectors:
+ description: List of label selectors to use to filter results.
+ type: list
+ elements: str
+ version_added: 2.0.0
+ continue_on_error:
+ description:
+ - Whether to continue on errors when multiple resources are defined.
+ type: bool
+ default: False
+ version_added: 2.0.0
+
+requirements:
+ - "python >= 3.6"
+ - "kubernetes >= 12.0.0"
+ - "PyYAML >= 3.11"
+"""
+
+EXAMPLES = r"""
+- name: Scale deployment up, and extend timeout
+ kubernetes.core.k8s_scale:
+ api_version: v1
+ kind: Deployment
+ name: elastic
+ namespace: myproject
+ replicas: 3
+ wait_timeout: 60
+
+- name: Scale deployment down when current replicas match
+ kubernetes.core.k8s_scale:
+ api_version: v1
+ kind: Deployment
+ name: elastic
+ namespace: myproject
+ current_replicas: 3
+ replicas: 2
+
+- name: Increase job parallelism
+ kubernetes.core.k8s_scale:
+ api_version: batch/v1
+ kind: job
+ name: pi-with-timeout
+ namespace: testing
+ replicas: 2
+
+# Match object using local file or inline definition
+
+- name: Scale deployment based on a file from the local filesystem
+ kubernetes.core.k8s_scale:
+ src: /myproject/elastic_deployment.yml
+ replicas: 3
+ wait: no
+
+- name: Scale deployment based on a template output
+ kubernetes.core.k8s_scale:
+ resource_definition: "{{ lookup('template', '/myproject/elastic_deployment.yml') | from_yaml }}"
+ replicas: 3
+ wait: no
+
+- name: Scale deployment based on a file from the Ansible controller filesystem
+ kubernetes.core.k8s_scale:
+ resource_definition: "{{ lookup('file', '/myproject/elastic_deployment.yml') | from_yaml }}"
+ replicas: 3
+ wait: no
+
+- name: Scale deployment using label selectors (continue operation in case error occured on one resource)
+ kubernetes.core.k8s_scale:
+ replicas: 3
+ kind: Deployment
+ namespace: test
+ label_selectors:
+ - app=test
+ continue_on_error: true
+"""
+
+RETURN = r"""
+result:
+ description:
+ - If a change was made, will return the patched object, otherwise returns the existing object.
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: Represents the REST resource this object represents.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: complex
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: complex
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: complex
+ duration:
+ description: elapsed time of task in seconds
+ returned: when C(wait) is true
+ type: int
+ sample: 48
+"""
+
+import copy
+
+try:
+ from kubernetes.dynamic.exceptions import NotFoundError
+except ImportError:
+ # Handled in module setup
+ pass
+
+from ansible.module_utils._text import to_native
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+ RESOURCE_ARG_SPEC,
+ NAME_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+ ResourceTimeout,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ diff_objects,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
+ get_waiter,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
+ create_definitions,
+)
+
+SCALE_ARG_SPEC = {
+ "replicas": {"type": "int", "required": True},
+ "current_replicas": {"type": "int"},
+ "resource_version": {},
+ "wait": {"type": "bool", "default": True},
+ "wait_timeout": {"type": "int", "default": 20},
+ "wait_sleep": {"type": "int", "default": 5},
+}
+
+
+def execute_module(client, module):
+ current_replicas = module.params.get("current_replicas")
+ replicas = module.params.get("replicas")
+ resource_version = module.params.get("resource_version")
+ definitions = create_definitions(module.params)
+ definition = definitions[0]
+ name = definition["metadata"].get("name")
+ namespace = definition["metadata"].get("namespace")
+ api_version = definition["apiVersion"]
+ kind = definition["kind"]
+ label_selectors = module.params.get("label_selectors")
+ if not label_selectors:
+ label_selectors = []
+ continue_on_error = module.params.get("continue_on_error")
+ wait = module.params.get("wait")
+ wait_time = module.params.get("wait_timeout")
+ wait_sleep = module.params.get("wait_sleep")
+ existing = None
+ existing_count = None
+ return_attributes = dict(result=dict())
+ if module._diff:
+ return_attributes["diff"] = dict()
+ if wait:
+ return_attributes["duration"] = 0
+
+ resource = client.resource(kind, api_version)
+ multiple_scale = False
+ try:
+ existing = resource.get(
+ name=name, namespace=namespace, label_selector=",".join(label_selectors)
+ )
+ if existing.kind.endswith("List"):
+ existing_items = existing.items
+ multiple_scale = len(existing_items) > 1
+ else:
+ existing_items = [existing]
+ except NotFoundError as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Failed to retrieve requested object: {0}".format(reason)
+ raise CoreException(msg) from e
+
+ if multiple_scale:
+ # when scaling multiple resource, the 'result' is changed to 'results' and is a list
+ return_attributes = {"results": []}
+ changed = False
+
+ def _continue_or_fail(error):
+ if multiple_scale and continue_on_error:
+ if "errors" not in return_attributes:
+ return_attributes["errors"] = []
+ return_attributes["errors"].append({"error": error, "failed": True})
+ else:
+ module.fail_json(msg=error, **return_attributes)
+
+ def _continue_or_exit(warn):
+ if multiple_scale:
+ return_attributes["results"].append({"warning": warn, "changed": False})
+ else:
+ module.exit_json(warning=warn, **return_attributes)
+
+ for existing in existing_items:
+ if kind.lower() == "job":
+ existing_count = existing.spec.parallelism
+ elif hasattr(existing.spec, "replicas"):
+ existing_count = existing.spec.replicas
+
+ if existing_count is None:
+ error = "Failed to retrieve the available count for object kind={0} name={1} namespace={2}.".format(
+ existing.kind, existing.metadata.name, existing.metadata.namespace
+ )
+ _continue_or_fail(error)
+ continue
+
+ if resource_version and resource_version != existing.metadata.resourceVersion:
+ warn = "expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
+ resource_version,
+ existing.metadata.resourceVersion,
+ existing.kind,
+ existing.metadata.name,
+ existing.metadata.namespace,
+ )
+ _continue_or_exit(warn)
+ continue
+
+ if current_replicas is not None and existing_count != current_replicas:
+ warn = "current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
+ current_replicas,
+ existing_count,
+ existing.kind,
+ existing.metadata.name,
+ existing.metadata.namespace,
+ )
+ _continue_or_exit(warn)
+ continue
+
+ if existing_count != replicas:
+ if kind.lower() == "job":
+ existing.spec.parallelism = replicas
+ result = {"changed": True}
+ if module.check_mode:
+ result["result"] = existing.to_dict()
+ else:
+ result["result"] = client.patch(
+ resource, existing.to_dict()
+ ).to_dict()
+ else:
+ try:
+ result = scale(
+ client,
+ module,
+ resource,
+ existing,
+ replicas,
+ wait,
+ wait_time,
+ wait_sleep,
+ )
+ except CoreException as e:
+ module.fail_json(msg=to_native(e))
+ changed = changed or result["changed"]
+ else:
+ name = existing.metadata.name
+ namespace = existing.metadata.namespace
+ existing = client.get(resource, name=name, namespace=namespace)
+ result = {"changed": False, "result": existing.to_dict()}
+ if module._diff:
+ result["diff"] = {}
+ if wait:
+ result["duration"] = 0
+ # append result to the return attribute
+ if multiple_scale:
+ return_attributes["results"].append(result)
+ else:
+ module.exit_json(**result)
+
+ module.exit_json(changed=changed, **return_attributes)
+
+
+def argspec():
+ args = copy.deepcopy(SCALE_ARG_SPEC)
+ args.update(RESOURCE_ARG_SPEC)
+ args.update(NAME_ARG_SPEC)
+ args.update(AUTH_ARG_SPEC)
+ args.update({"label_selectors": {"type": "list", "elements": "str", "default": []}})
+ args.update(({"continue_on_error": {"type": "bool", "default": False}}))
+ return args
+
+
+def scale(
+ client,
+ module,
+ resource,
+ existing_object,
+ replicas,
+ wait,
+ wait_time,
+ wait_sleep,
+):
+ name = existing_object.metadata.name
+ namespace = existing_object.metadata.namespace
+ kind = existing_object.kind
+
+ if not hasattr(resource, "scale"):
+ raise CoreException(
+ "Cannot perform scale on resource of kind {0}".format(resource.kind)
+ )
+
+ scale_obj = {
+ "kind": kind,
+ "metadata": {"name": name, "namespace": namespace},
+ "spec": {"replicas": replicas},
+ }
+
+ existing = client.get(resource, name=name, namespace=namespace)
+
+ result = dict()
+ if module.check_mode:
+ k8s_obj = copy.deepcopy(existing.to_dict())
+ k8s_obj["spec"]["replicas"] = replicas
+ if wait:
+ result["duration"] = 0
+ result["result"] = k8s_obj
+ else:
+ try:
+ resource.scale.patch(body=scale_obj)
+ except Exception as e:
+ reason = e.body if hasattr(e, "body") else e
+ msg = "Scale request failed: {0}".format(reason)
+ raise CoreException(msg) from e
+
+ k8s_obj = client.get(resource, name=name, namespace=namespace).to_dict()
+ result["result"] = k8s_obj
+ if wait:
+ waiter = get_waiter(client, resource)
+ success, result["result"], result["duration"] = waiter.wait(
+ timeout=wait_time,
+ sleep=wait_sleep,
+ name=name,
+ namespace=namespace,
+ )
+ if not success:
+ raise ResourceTimeout("Resource scaling timed out", **result)
+
+ match, diffs = diff_objects(existing.to_dict(), result["result"])
+ result["changed"] = not match
+ if module._diff:
+ result["diff"] = diffs
+
+ return result
+
+
+def main():
+ mutually_exclusive = [
+ ("resource_definition", "src"),
+ ]
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ try:
+ client = get_api_client(module=module)
+ execute_module(client, module)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_service.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_service.py
new file mode 100644
index 00000000..1eed29bd
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_service.py
@@ -0,0 +1,265 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018, KubeVirt Team <@kubevirt>
+# 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_service
+
+short_description: Manage Services on Kubernetes
+
+author: KubeVirt Team (@kubevirt)
+
+description:
+ - Use Kubernetes Python SDK to manage Services on Kubernetes
+
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+ - kubernetes.core.k8s_resource_options
+ - kubernetes.core.k8s_state_options
+
+options:
+ merge_type:
+ description:
+ - Whether to override the default patch merge approach with a specific type. By default, the strategic
+ merge will typically be used.
+ - For example, Custom Resource Definitions typically aren't updatable by the usual strategic merge. You may
+ want to use C(merge) if you see "strategic merge patch format is not supported"
+ - See U(https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
+ - If more than one C(merge_type) is given, the merge_types will be tried in order
+ - This defaults to C(['strategic-merge', 'merge']), which is ideal for using the same parameters
+ on resource kinds that combine Custom Resources and built-in resources.
+ choices:
+ - json
+ - merge
+ - strategic-merge
+ type: list
+ elements: str
+ name:
+ description:
+ - Use to specify a Service object name.
+ required: true
+ type: str
+ namespace:
+ description:
+ - Use to specify a Service object namespace.
+ required: true
+ type: str
+ type:
+ description:
+ - Specifies the type of Service to create.
+ - See U(https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types)
+ choices:
+ - NodePort
+ - ClusterIP
+ - LoadBalancer
+ - ExternalName
+ type: str
+ ports:
+ description:
+ - A list of ports to expose.
+ - U(https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services)
+ type: list
+ elements: dict
+ selector:
+ description:
+ - Label selectors identify objects this Service should apply to.
+ - U(https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
+ type: dict
+ apply:
+ description:
+ - C(apply) compares the desired resource definition with the previously supplied resource definition,
+ ignoring properties that are automatically generated
+ - C(apply) works better with Services than 'force=yes'
+ - mutually exclusive with C(merge_type)
+ default: False
+ type: bool
+
+requirements:
+ - python >= 3.6
+ - kubernetes >= 12.0.0
+"""
+
+EXAMPLES = r"""
+- name: Expose https port with ClusterIP
+ kubernetes.core.k8s_service:
+ state: present
+ name: test-https
+ namespace: default
+ ports:
+ - port: 443
+ protocol: TCP
+ selector:
+ key: special
+
+- name: Expose https port with ClusterIP using spec
+ kubernetes.core.k8s_service:
+ state: present
+ name: test-https
+ namespace: default
+ inline:
+ spec:
+ ports:
+ - port: 443
+ protocol: TCP
+ selector:
+ key: special
+"""
+
+RETURN = r"""
+result:
+ description:
+ - The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: Always 'Service'.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: complex
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: complex
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: complex
+"""
+
+import copy
+
+from collections import defaultdict
+
+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,
+ COMMON_ARG_SPEC,
+ RESOURCE_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
+ create_definitions,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
+ perform_action,
+)
+
+
+SERVICE_ARG_SPEC = {
+ "apply": {"type": "bool", "default": False},
+ "name": {"required": True},
+ "namespace": {"required": True},
+ "merge_type": {
+ "type": "list",
+ "elements": "str",
+ "choices": ["json", "merge", "strategic-merge"],
+ },
+ "selector": {"type": "dict"},
+ "type": {
+ "type": "str",
+ "choices": ["NodePort", "ClusterIP", "LoadBalancer", "ExternalName"],
+ },
+ "ports": {"type": "list", "elements": "dict"},
+}
+
+
+def merge_dicts(x, y):
+ for k in set(x.keys()).union(y.keys()):
+ if k in x and k in y:
+ if isinstance(x[k], dict) and isinstance(y[k], dict):
+ yield (k, dict(merge_dicts(x[k], y[k])))
+ else:
+ yield (k, y[k] if y[k] else x[k])
+ elif k in x:
+ yield (k, x[k])
+ else:
+ yield (k, y[k])
+
+
+def argspec():
+ """argspec property builder"""
+ argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
+ argument_spec.update(COMMON_ARG_SPEC)
+ argument_spec.update(RESOURCE_ARG_SPEC)
+ argument_spec.update(SERVICE_ARG_SPEC)
+ return argument_spec
+
+
+def execute_module(svc):
+ """Module execution"""
+ module = svc.module
+ api_version = "v1"
+ selector = module.params.get("selector")
+ service_type = module.params.get("type")
+ ports = module.params.get("ports")
+
+ definition = defaultdict(defaultdict)
+
+ definition["kind"] = "Service"
+ definition["apiVersion"] = api_version
+
+ def_spec = definition["spec"]
+ def_spec["type"] = service_type
+ def_spec["ports"] = ports
+ def_spec["selector"] = selector
+
+ def_meta = definition["metadata"]
+ def_meta["name"] = module.params.get("name")
+ def_meta["namespace"] = module.params.get("namespace")
+
+ definitions = create_definitions(module.params)
+
+ # 'resource_definition:' has lower priority than module parameters
+ definition = dict(merge_dicts(definitions[0], definition))
+
+ result = perform_action(svc, definition, module.params)
+
+ module.exit_json(**result)
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ supports_check_mode=True,
+ )
+
+ try:
+ client = get_api_client(module=module)
+ svc = K8sService(client, module)
+ execute_module(svc)
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/plugins/modules/k8s_taint.py b/ansible_collections/kubernetes/core/plugins/modules/k8s_taint.py
new file mode 100644
index 00000000..bfa80db5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/plugins/modules/k8s_taint.py
@@ -0,0 +1,313 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2021, Alina Buzachis <@alinabuzachis>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: k8s_taint
+short_description: Taint a node in a Kubernetes/OpenShift cluster
+version_added: "2.3.0"
+author: Alina Buzachis (@alinabuzachis)
+description:
+ - Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
+ - Untaint will remove taints from nodes as needed.
+extends_documentation_fragment:
+ - kubernetes.core.k8s_auth_options
+options:
+ state:
+ description:
+ - Determines whether to add or remove taints.
+ type: str
+ default: present
+ choices: [ present, absent ]
+ name:
+ description:
+ - The name of the node.
+ required: true
+ type: str
+ taints:
+ description:
+ - List containing the taints.
+ type: list
+ required: true
+ elements: dict
+ suboptions:
+ key:
+ description:
+ - The taint key to be applied to a node.
+ type: str
+ value:
+ description:
+ - The taint value corresponding to the taint key.
+ type: str
+ effect:
+ description:
+ - The effect of the taint on Pods that do not tolerate the taint.
+ - Required when I(state=present).
+ type: str
+ choices: [ NoSchedule, NoExecute, PreferNoSchedule ]
+ replace:
+ description:
+ - If C(true), allow taints to be replaced.
+ required: false
+ default: false
+ type: bool
+requirements:
+ - python >= 3.6
+ - kubernetes >= 12.0.0
+"""
+
+EXAMPLES = r"""
+- name: Taint node "foo"
+ kubernetes.core.k8s_taint:
+ state: present
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+
+- name: Taint node "foo"
+ kubernetes.core.k8s_taint:
+ state: present
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+ value: "value1"
+ - effect: NoSchedule
+ key: "key1"
+ value: "value1"
+
+- name: Remove taint from "foo".
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: foo
+ taints:
+ - effect: NoExecute
+ key: "key1"
+ value: "value1"
+"""
+
+RETURN = r"""
+result:
+ description:
+ - The tainted Node object. Will be empty in the case of a deletion.
+ returned: success
+ type: complex
+ contains:
+ api_version:
+ description: The versioned schema of this representation of an object.
+ returned: success
+ type: str
+ kind:
+ description: Represents the REST resource this object represents.
+ returned: success
+ type: str
+ metadata:
+ description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
+ returned: success
+ type: complex
+ spec:
+ description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
+ returned: success
+ type: complex
+ status:
+ description: Current status details for the object.
+ returned: success
+ type: complex
+"""
+
+import copy
+
+from ansible.module_utils._text import to_native
+
+from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
+ AnsibleModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
+ AUTH_ARG_SPEC,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
+ CoreException,
+)
+
+try:
+ from kubernetes.client.api import core_v1_api
+ from kubernetes.client.exceptions import ApiException
+except ImportError:
+ # ImportErrors are handled during module setup
+ pass
+
+
+def _equal_dicts(a, b):
+ keys = ["key", "effect"]
+ if "effect" not in set(a).intersection(b):
+ keys.remove("effect")
+
+ return all((a[x] == b[x] for x in keys))
+
+
+def _get_difference(a, b):
+ return [
+ a_item for a_item in a if not any(_equal_dicts(a_item, b_item) for b_item in b)
+ ]
+
+
+def _get_intersection(a, b):
+ return [a_item for a_item in a if any(_equal_dicts(a_item, b_item) for b_item in b)]
+
+
+def _update_exists(a, b):
+ return any(
+ (
+ any(
+ _equal_dicts(a_item, b_item)
+ and a_item.get("value") != b_item.get("value")
+ for b_item in b
+ )
+ for a_item in a
+ )
+ )
+
+
+def argspec():
+ argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
+ argument_spec.update(
+ dict(
+ state=dict(type="str", choices=["present", "absent"], default="present"),
+ name=dict(type="str", required=True),
+ taints=dict(type="list", required=True, elements="dict"),
+ replace=dict(type="bool", default=False),
+ )
+ )
+
+ return argument_spec
+
+
+class K8sTaintAnsible:
+ def __init__(self, module, client):
+ self.module = module
+ self.api_instance = core_v1_api.CoreV1Api(client.client)
+ self.changed = False
+
+ def get_node(self, name):
+ try:
+ node = self.api_instance.read_node(name=name)
+ except ApiException as exc:
+ if exc.reason == "Not Found":
+ self.module.fail_json(msg="Node '{0}' has not been found.".format(name))
+ self.module.fail_json(
+ msg="Failed to retrieve node '{0}' due to: {1}".format(
+ name, exc.reason
+ ),
+ status=exc.status,
+ )
+ except Exception as exc:
+ self.module.fail_json(
+ msg="Failed to retrieve node '{0}' due to: {1}".format(
+ name, to_native(exc)
+ )
+ )
+
+ return node
+
+ def patch_node(self, taints):
+ body = {"spec": {"taints": taints}}
+
+ try:
+ result = self.api_instance.patch_node(
+ name=self.module.params.get("name"), body=body
+ )
+ except Exception as exc:
+ self.module.fail_json(
+ msg="Failed to patch node due to: {0}".format(to_native(exc))
+ )
+
+ return result.to_dict()
+
+ def execute_module(self):
+ result = {"result": {}}
+ state = self.module.params.get("state")
+ taints = self.module.params.get("taints")
+ name = self.module.params.get("name")
+
+ node = self.get_node(name)
+ existing_taints = node.spec.to_dict().get("taints") or []
+ diff = _get_difference(taints, existing_taints)
+
+ if state == "present":
+ if diff:
+ # There are new taints to be added
+ self.changed = True
+ if self.module.check_mode:
+ self.module.exit_json(changed=self.changed, **result)
+
+ if self.module.params.get("replace"):
+ # Patch with the new taints
+ result["result"] = self.patch_node(taints=taints)
+ self.module.exit_json(changed=self.changed, **result)
+
+ result["result"] = self.patch_node(
+ taints=[*_get_difference(existing_taints, taints), *taints]
+ )
+ else:
+ # No new taints to be added, but maybe there is something to be updated
+ if _update_exists(existing_taints, taints):
+ self.changed = True
+ if self.module.check_mode:
+ self.module.exit_json(changed=self.changed, **result)
+ result["result"] = self.patch_node(
+ taints=[*_get_difference(existing_taints, taints), *taints]
+ )
+ else:
+ result["result"] = node.to_dict()
+ elif state == "absent":
+ # Nothing to be removed
+ if not existing_taints:
+ result["result"] = node.to_dict()
+ if not diff:
+ self.changed = True
+ if self.module.check_mode:
+ self.module.exit_json(changed=self.changed, **result)
+ self.patch_node(taints=_get_difference(existing_taints, taints))
+ else:
+ if _get_intersection(existing_taints, taints):
+ self.changed = True
+ if self.module.check_mode:
+ self.module.exit_json(changed=self.changed, **result)
+ self.patch_node(taints=_get_difference(existing_taints, taints))
+ else:
+ self.module.exit_json(changed=self.changed, **result)
+
+ self.module.exit_json(changed=self.changed, **result)
+
+
+def main():
+ module = AnsibleK8SModule(
+ module_class=AnsibleModule,
+ argument_spec=argspec(),
+ supports_check_mode=True,
+ )
+ try:
+ client = get_api_client(module)
+ k8s_taint = K8sTaintAnsible(module, client.client)
+ k8s_taint.execute_module()
+ except CoreException as e:
+ module.fail_from_exception(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/requirements.txt b/ansible_collections/kubernetes/core/requirements.txt
new file mode 100644
index 00000000..cea80595
--- /dev/null
+++ b/ansible_collections/kubernetes/core/requirements.txt
@@ -0,0 +1,3 @@
+kubernetes>=12.0.0
+requests-oauthlib
+jsonpatch
diff --git a/ansible_collections/kubernetes/core/setup.cfg b/ansible_collections/kubernetes/core/setup.cfg
new file mode 100644
index 00000000..664feaae
--- /dev/null
+++ b/ansible_collections/kubernetes/core/setup.cfg
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 160
+ignore = W503,E402
+exclude = .cache,.git,.tox,tests/output
diff --git a/ansible_collections/kubernetes/core/test-requirements.txt b/ansible_collections/kubernetes/core/test-requirements.txt
new file mode 100644
index 00000000..c9068609
--- /dev/null
+++ b/ansible_collections/kubernetes/core/test-requirements.txt
@@ -0,0 +1,7 @@
+kubernetes-validate
+coverage==4.5.4
+mock
+pytest
+pytest-xdist
+pytest-mock
+pytest-forked
diff --git a/ansible_collections/kubernetes/core/tests/config.yml b/ansible_collections/kubernetes/core/tests/config.yml
new file mode 100644
index 00000000..9e402bda
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/config.yml
@@ -0,0 +1,2 @@
+modules:
+ python_requires: ">=3.6"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm/aliases
new file mode 100644
index 00000000..fdc6dfc7
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/aliases
@@ -0,0 +1,4 @@
+time=100
+helm_info
+helm_repository
+helm_template
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/defaults/main.yml
new file mode 100644
index 00000000..ae860ce8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/defaults/main.yml
@@ -0,0 +1,26 @@
+---
+helm_default_archive_name: "helm-{{ helm_version }}-{{ ansible_system | lower }}-amd64.tar.gz"
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
+
+chart_test: "ingress-nginx"
+chart_test_local_path: "nginx-ingress"
+chart_test_version: 4.2.4
+chart_test_version_local_path: 1.32.0
+chart_test_version_upgrade: 4.2.5
+chart_test_version_upgrade_local_path: 1.33.0
+chart_test_repo: "https://kubernetes.github.io/ingress-nginx"
+chart_test_git_repo: "http://github.com/helm/charts.git"
+chart_test_values:
+ revisionHistoryLimit: 0
+ myValue: "changed"
+
+test_namespace:
+ - "helm-test-crds"
+ - "helm-uninstall"
+ - "helm-read-envvars"
+ - "helm-dep-update"
+ - "helm-local-path-001"
+ - "helm-local-path-002"
+ - "helm-local-path-003"
+ - "helm-from-repository"
+ - "helm-from-url"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/Chart.yaml
new file mode 100644
index 00000000..34aed289
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v2
+name: appversionless-chart
+description: A chart used in molecule tests
+type: application
+version: 0.2.0
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml
new file mode 100644
index 00000000..69bf7f64
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-chart-configmap
+data:
+ myValue: {{ default "test" .Values.myValue }}
+ myOtherValue: {{ default "foo" .Values.myOtherValue }}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/Chart.yaml
new file mode 100644
index 00000000..c308a00a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v2
+name: appversionless-chart
+description: A chart used in molecule tests
+type: application
+version: 0.1.0
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml
new file mode 100644
index 00000000..7ef9931d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-chart-configmap
+data:
+ myValue: {{ default "test" .Values.myValue }}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/Chart.yaml
new file mode 100644
index 00000000..663f0ec8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/Chart.yaml
@@ -0,0 +1,10 @@
+apiVersion: v2
+name: dep_up
+description: A Helm chart for molecule test
+type: application
+version: 0.1.0
+appVersion: "default"
+dependencies:
+ - name: test-chart
+ repository: file://../test-chart
+ version: "0.1.0"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/values.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/values.yaml
new file mode 100644
index 00000000..36072811
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/dep-up/values.yaml
@@ -0,0 +1,2 @@
+chart-test:
+ myValue: helm update dependency test
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/Chart.yaml
new file mode 100644
index 00000000..0676e9f6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: test-chart
+description: A chart used in molecule tests
+type: application
+version: 0.2.0
+appVersion: "default"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml
new file mode 100644
index 00000000..69bf7f64
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-chart-configmap
+data:
+ myValue: {{ default "test" .Values.myValue }}
+ myOtherValue: {{ default "foo" .Values.myOtherValue }}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/Chart.yaml
new file mode 100644
index 00000000..5d09a08c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: test-chart
+description: A chart used in molecule tests
+type: application
+version: 0.1.0
+appVersion: "default"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/templates/configmap.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/templates/configmap.yaml
new file mode 100644
index 00000000..7ef9931d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-chart/templates/configmap.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-chart-configmap
+data:
+ myValue: {{ default "test" .Values.myValue }}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/Chart.yaml
new file mode 100644
index 00000000..05c096f9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v2
+name: test-crds
+description: A chart with CRDs
+type: application
+version: 0.1.0
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/crds/crd.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/crds/crd.yaml
new file mode 100644
index 00000000..ac285313
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/test-crds/crds/crd.yaml
@@ -0,0 +1,21 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: foos.ansible.com
+spec:
+ group: ansible.com
+ versions:
+ - name: v1
+ served: true
+ storage: true
+ schema:
+ openAPIV3Schema:
+ type: object
+ properties:
+ foobar:
+ type: string
+ scope: Namespaced
+ names:
+ plural: foos
+ singular: foo
+ kind: Foo
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/values.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/values.yaml
new file mode 100644
index 00000000..7b057068
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/files/values.yaml
@@ -0,0 +1,2 @@
+---
+revisionHistoryLimit: 0
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/library/helm_test_version.py b/ansible_collections/kubernetes/core/tests/integration/targets/helm/library/helm_test_version.py
new file mode 100644
index 00000000..dfd9a086
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/library/helm_test_version.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+---
+module: helm_test_version
+short_description: check helm executable version
+author:
+ - Aubin Bikouo (@abikouo)
+requirements:
+ - "helm (https://github.com/helm/helm/releases)"
+description:
+ - validate version of helm binary is lower than the specified version.
+options:
+ binary_path:
+ description:
+ - The path of a helm binary to use.
+ required: false
+ type: path
+ version:
+ description:
+ - version to test against helm binary.
+ type: str
+ default: 3.7.0
+"""
+
+EXAMPLES = r"""
+- name: validate helm binary version is lower than 3.5.0
+ helm_test_version:
+ binary_path: path/to/helm
+ version: "3.5.0"
+"""
+
+RETURN = r"""
+message:
+ type: str
+ description: Text message describing the test result.
+ returned: always
+ sample: 'version installed: 3.4.5 is lower than version 3.5.0'
+result:
+ type: bool
+ description: Test result.
+ returned: always
+ sample: 1
+"""
+
+import re
+from ansible_collections.kubernetes.core.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ binary_path=dict(type="path"),
+ version=dict(type="str", default="3.7.0"),
+ ),
+ )
+
+ bin_path = module.params.get("binary_path")
+ version = module.params.get("version")
+
+ if bin_path is not None:
+ helm_cmd_common = bin_path
+ else:
+ helm_cmd_common = "helm"
+
+ helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
+ rc, out, err = module.run_command([helm_cmd_common, "version"])
+ if rc != 0:
+ module.fail_json(msg="helm version failed.", err=err, out=out, rc=rc)
+
+ m = re.match(r'version.BuildInfo{Version:"v([0-9\.]*)",', out)
+ installed_version = m.group(1)
+
+ message = "version installed: %s" % installed_version
+ if LooseVersion(installed_version) < LooseVersion(version):
+ message += " is lower than version %s" % version
+ module.exit_json(changed=False, result=True, message=message)
+ else:
+ message += " is greater than version %s" % version
+ module.exit_json(changed=False, result=False, message=message)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/meta/main.yml
new file mode 100644
index 00000000..79869fd3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/meta/main.yml
@@ -0,0 +1,5 @@
+---
+collections:
+ - kubernetes.core
+dependencies:
+ - remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/install.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/install.yml
new file mode 100644
index 00000000..248925ee
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/install.yml
@@ -0,0 +1,15 @@
+---
+- name: Init Helm folders
+ file:
+ path: /tmp/helm/
+ state: directory
+
+- name: Unarchive Helm binary
+ unarchive:
+ src: 'https://get.helm.sh/{{ helm_archive_name | default(helm_default_archive_name) }}'
+ dest: /tmp/helm/
+ remote_src: yes
+ retries: 10
+ delay: 5
+ register: result
+ until: result is not failed
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/main.yml
new file mode 100644
index 00000000..9d8dda3e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Run tests
+ include_tasks: run_test.yml
+ loop_control:
+ loop_var: helm_version
+ with_items:
+ - "v3.7.0"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/run_test.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/run_test.yml
new file mode 100644
index 00000000..545b25c8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/run_test.yml
@@ -0,0 +1,43 @@
+---
+- name: Ensure helm is not installed
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - "/tmp/helm"
+
+- name: Check failed if helm is not installed
+ include_tasks: test_helm_not_installed.yml
+
+- name: "Install {{ helm_version }}"
+ include_role:
+ name: install_helm
+
+- name: "Ensure we honor the environment variables"
+ include_tasks: test_read_envvars.yml
+
+- name: Deploy charts
+ include_tasks: "tests_chart/{{ test_chart_type }}.yml"
+ loop_control:
+ loop_var: test_chart_type
+ with_items:
+ - from_local_path
+ - from_repository
+ - from_url
+
+- name: test helm dependency update
+ include_tasks: test_up_dep.yml
+
+- name: Test helm uninstall
+ include_tasks: test_helm_uninstall.yml
+
+# https://github.com/ansible-collections/community.kubernetes/issues/296
+- name: Test Skip CRDS feature in helm chart install
+ include_tasks: test_crds.yml
+
+- name: Clean helm install
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - "/tmp/helm/"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_crds.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_crds.yml
new file mode 100644
index 00000000..0534869b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_crds.yml
@@ -0,0 +1,99 @@
+---
+- name: Test CRDs
+ vars:
+ test_chart: "test-crds"
+ helm_namespace: "{{ test_namespace[0] }}"
+ block:
+ - name: Create namespace
+ k8s:
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+
+ - name: Copy test chart
+ copy:
+ src: "{{ test_chart }}"
+ dest: "/tmp/helm_test_crds/"
+
+ - 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
+ skip_crds: true
+ register: install
+
+ - assert:
+ that:
+ - install is changed
+ - install.status.name == "test-crds"
+
+ - name: Fail to create custom resource
+ k8s:
+ definition:
+ apiVersion: ansible.com/v1
+ kind: Foo
+ metadata:
+ namespace: "{{ helm_namespace }}"
+ name: test-foo
+ foobar: footest
+ ignore_errors: true
+ register: result
+
+ - assert:
+ that:
+ - result is failed
+ - "result.msg.startswith('Failed to find exact match for ansible.com/v1.Foo')"
+
+ # Helm won't install CRDs into an existing release, so we need to delete this, first
+ - name: Uninstall chart
+ helm:
+ 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
+
+ - name: Create custom resource
+ k8s:
+ definition:
+ apiVersion: ansible.com/v1
+ kind: Foo
+ metadata:
+ namespace: "{{ helm_namespace }}"
+ name: test-foo
+ foobar: footest
+ register: result
+
+ - assert:
+ that:
+ - result is changed
+ - result.result.foobar == "footest"
+
+ always:
+ - name: Remove chart
+ file:
+ path: "/tmp/helm_test_crds"
+ state: absent
+ ignore_errors: true
+
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ state: absent
+ ignore_errors: true
+
+ # CRDs aren't deleted with a namespace, so we need to manually delete it
+ - name: Remove CRD
+ k8s:
+ kind: CustomResourceDefinition
+ name: foos.ansible.com
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_not_installed.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_not_installed.yml
new file mode 100644
index 00000000..2f2fd27e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_not_installed.yml
@@ -0,0 +1,15 @@
+---
+- name: Failed test when helm is not installed
+ helm:
+ binary_path: "{{ helm_binary}}_fake"
+ name: test
+ chart_ref: "{{ chart_test }}"
+ namespace: "helm-test"
+ ignore_errors: yes
+ register: helm_missing_binary
+
+- name: Assert that helm is not installed
+ assert:
+ that:
+ - helm_missing_binary is failed
+ - "'No such file or directory' in helm_missing_binary.msg"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_uninstall.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_uninstall.yml
new file mode 100644
index 00000000..e82891e4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_helm_uninstall.yml
@@ -0,0 +1,80 @@
+- name: validate helm version lower than 3.7.0
+ helm_test_version:
+ binary_path: "{{ helm_binary }}"
+ version: "3.7.0"
+ register: test_version
+
+- vars:
+ chart_source: "https://github.com/kubernetes/kube-state-metrics/releases/download/kube-state-metrics-helm-chart-2.13.3/kube-state-metrics-2.13.3.tgz"
+ chart_name: "test-wait-uninstall"
+ helm_namespace: "{{ test_namespace[1] }}"
+ block:
+
+ - name: Install chart
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_name }}"
+ chart_ref: "{{ chart_source }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+
+ - name: Delete chart with wait
+ helm:
+ state: absent
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_name }}"
+ namespace: "{{ helm_namespace }}"
+ wait: yes
+ register: uninstall
+
+ - name: assert warning has been raised
+ assert:
+ that:
+ - uninstall.warnings
+
+ - name: Create temp directory
+ tempfile:
+ state: directory
+ suffix: .test
+ register: _result
+
+ - set_fact:
+ helm_tmp_dir: "{{ _result.path }}"
+
+ - name: Unarchive Helm binary
+ unarchive:
+ src: 'https://get.helm.sh/helm-v3.7.0-linux-amd64.tar.gz'
+ dest: "{{ helm_tmp_dir }}"
+ remote_src: yes
+
+ - name: Install chart
+ helm:
+ binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
+ name: "{{ chart_name }}"
+ chart_ref: "{{ chart_source }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+
+ - name: uninstall chart again using recent version
+ helm:
+ state: absent
+ binary_path: "{{ helm_tmp_dir }}/linux-amd64/helm"
+ name: "{{ chart_name }}"
+ namespace: "{{ helm_namespace }}"
+ wait: yes
+ register: uninstall
+
+ always:
+ - name: Delete temp directory
+ file:
+ path: "{{ helm_tmp_dir }}"
+ state: absent
+ ignore_errors: true
+
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ state: absent
+ ignore_errors: true
+ when: test_version.result
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_read_envvars.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_read_envvars.yml
new file mode 100644
index 00000000..09c0d355
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_read_envvars.yml
@@ -0,0 +1,12 @@
+- name: Pass a bogus server through the K8S_AUTH_HOST environment variable and ensure helm fails as expected
+ helm:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ name: does-not-exist
+ namespace: "{{ helm_namespace }}"
+ environment:
+ K8S_AUTH_HOST: somewhere
+ vars:
+ helm_namespace: "{{ test_namespace[2] }}"
+ register: _helm_result
+ failed_when: '"http://somewhere/version" not in _helm_result.stderr'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_up_dep.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_up_dep.yml
new file mode 100644
index 00000000..39a14460
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/test_up_dep.yml
@@ -0,0 +1,160 @@
+# Helm module
+- name: "Test dependency update for helm module"
+ vars:
+ helm_namespace: "{{ test_namespace[3] }}"
+ block:
+ - name: copy chart
+ copy:
+ src: "{{ item }}"
+ dest: /tmp
+ loop:
+ - test-chart
+ - dep-up
+
+ - name: "Test chart with dependency_update false"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test
+ chart_ref: "/tmp/test-chart"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ dependency_update: false
+ create_namespace: yes
+ register: release
+
+ - name: "Get stats of the subchart"
+ stat:
+ path: "/tmp/test-chart/Chart.lock"
+ register: stat_result
+
+ - name: "Check if the subchart not exist in chart"
+ assert:
+ that:
+ - not stat_result.stat.exists
+ success_msg: "subchart not exist in the chart directory"
+ fail_msg: "subchart exist in the charts directory"
+
+ - name: "Test chart without dependencies block and dependency_update true"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test
+ chart_ref: "/tmp/test-chart"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: yes
+ dependency_update: true
+ ignore_errors: true
+ register: release
+
+ - assert:
+ that:
+ - release.warnings[0] == "There is no dependencies block defined in Chart.yaml. Dependency update will not be performed. Please consider add dependencies block or disable dependency_update to remove this warning."
+ success_msg: "warning when there is no dependencies block with dependency_update enabled"
+
+ - name: "Test chart with dependencies block and dependency_update true"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test
+ chart_ref: "/tmp/dep-up"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ dependency_update: true
+ create_namespace: yes
+ register: release
+
+ - name: "Get stats of the subchart"
+ stat:
+ path: "/tmp/dep-up/Chart.lock"
+ register: stat_result
+
+ - name: "Check if the subchart exists in chart"
+ assert:
+ that:
+ - stat_result.stat.exists
+ success_msg: "subchart exist in the chart directory"
+ fail_msg: "subchart not exist in the charts directory"
+ always:
+ - name: Remove helm namespace
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ state: absent
+
+ - name: "Remove charts"
+ file:
+ state: absent
+ path: "/tmp/{{ item }}"
+ loop:
+ - test-chart
+ - dep-up
+
+# Helm_template module
+- name: "Test dependency update for helm_template module"
+ block:
+ - name: copy chart
+ copy:
+ src: "{{ item }}"
+ dest: /tmp
+ loop:
+ - test-chart
+ - dep-up
+
+ - name: Test Helm dependency update true
+ helm_template:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: "/tmp/dep-up"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ dependency_update: true
+ output_dir: "/tmp"
+ register: result
+
+ - name: "Get stats of the subchart"
+ stat:
+ path: "{{ item }}"
+ register: stat_result
+ loop:
+ - /tmp/dep-up/Chart.lock
+ - /tmp/dep_up/charts/test-chart/templates/configmap.yaml
+
+ - name: "Check if the subchart exist in chart"
+ assert:
+ that:
+ - stat_result.results[0].stat.exists
+ - stat_result.results[1].stat.exists
+ success_msg: "subchart exist in the charts directory"
+ fail_msg: "There is no Subchart pulled"
+
+ - name: Test Helm subchart not pulled when dependency_update false for helm_template
+ helm_template:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: "/tmp/test-chart"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ dependency_update: false
+ output_dir: "/tmp"
+ register: result
+
+ - name: "Get stats of the subchart"
+ stat:
+ path: "{{ item }}"
+ register: stat_result
+ loop:
+ - /tmp/test-chart/Chart.lock
+ - /tmp/test-chart/templates/configmap.yaml
+
+ - name: "Check if the subchart not exist in chart"
+ assert:
+ that:
+ - not stat_result.results[0].stat.exists
+ - stat_result.results[1].stat.exists
+ success_msg: "subchart not exist in the charts directory"
+ fail_msg: "There is no Subchart pulled"
+ always:
+
+ - name: "Remove charts"
+ file:
+ state: absent
+ path: "/tmp/{{ item }}"
+ loop:
+ - test-chart
+ - dep-up
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart.yml
new file mode 100644
index 00000000..a0227215
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart.yml
@@ -0,0 +1,426 @@
+---
+- name: Chart tests
+ vars:
+ chart_release_name: "test-{{ chart_name | default(source) }}"
+ chart_release_replaced_name: "test-{{ chart_name | default(source) }}-001"
+ block:
+ - name: Create temp directory
+ tempfile:
+ state: directory
+ register: tmpdir
+
+ - name: Set temp directory fact
+ set_fact:
+ temp_dir: "{{ tmpdir.path }}"
+
+ - name: Check helm_info empty
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ register: empty_info
+
+ - name: "Assert that no charts are installed with helm_info"
+ assert:
+ that:
+ - empty_info.status is undefined
+
+ - name: "Install fail {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ ignore_errors: yes
+ register: install_fail
+
+ - name: "Assert that Install fail {{ chart_test }} from {{ source }}"
+ assert:
+ that:
+ - install_fail is failed
+ - "'Error: create: failed to create: namespaces \"' + helm_namespace + '\" not found' in install_fail.stderr"
+
+ - name: "Install {{ chart_test }} from {{ source }} in check mode"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ register: install_check_mode
+ check_mode: true
+
+ - name: "Assert that {{ chart_test }} chart is installed from {{ source }} in check mode"
+ assert:
+ that:
+ - install_check_mode is changed
+ - install_check_mode.status is defined
+ - install_check_mode.status.values is defined
+
+ - name: "Install {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is installed from {{ source }}"
+ assert:
+ that:
+ - install is changed
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - install.status.status | lower == 'deployed'
+
+ - name: Check helm_info content
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ register: content_info
+
+ - name: Check helm_info content using release_state
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ release_state:
+ - deployed
+ register: release_state_content_info
+
+ - name: "Assert that {{ chart_test }} is installed from {{ source }} with helm_info"
+ assert:
+ that:
+ - content_info.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - content_info.status.status | lower == 'deployed'
+ - release_state_content_info.status.status | lower == 'deployed'
+
+ - name: Check idempotency
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Assert idempotency
+ assert:
+ that:
+ - install is not changed
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - install.status.status | lower == 'deployed'
+
+ - name: "Add vars to {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ values: "{{ chart_test_values }}"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is upgraded with new var from {{ source }}"
+ assert:
+ that:
+ - install is changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - "install.status['values'].revisionHistoryLimit == 0"
+
+ - name: Check idempotency after adding vars
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ values: "{{ chart_test_values }}"
+ register: install
+
+ - name: Assert idempotency after add vars
+ assert:
+ that:
+ - install is not changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - "install.status['values'].revisionHistoryLimit == 0"
+
+ - name: "Remove Vars to {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is upgraded with new var from {{ source }}"
+ assert:
+ that:
+ - install is changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - install.status['values'] == {}
+
+ - name: Check idempotency after removing vars
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Assert idempotency after removing vars
+ assert:
+ that:
+ - install is not changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - install.status['values'] == {}
+
+ - name: "Upgrade {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source_upgrade | default(chart_source) }}"
+ chart_version: "{{ chart_source_version_upgrade | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is upgraded with new version from {{ source }}"
+ assert:
+ that:
+ - install is changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version_upgrade }}"
+
+ - name: Check idempotency after upgrade
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source_upgrade | default(chart_source) }}"
+ chart_version: "{{ chart_source_version_upgrade | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Assert idempotency after upgrade
+ assert:
+ that:
+ - install is not changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version_upgrade }}"
+
+ - name: "Remove {{ chart_test }} from {{ source }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is removed from {{ source }}"
+ assert:
+ that:
+ - install is changed
+
+ - name: Check idempotency after remove
+ helm:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Assert idempotency
+ assert:
+ that:
+ - install is not changed
+
+ # Test --replace
+ - name: Install chart for replace option
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_replaced_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is installed from {{ source }}"
+ assert:
+ that:
+ - install is changed
+
+ - name: "Remove {{ chart_release_replaced_name }} with --purge"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ name: "{{ chart_release_replaced_name }}"
+ purge: False
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Check if chart is removed
+ assert:
+ that:
+ - install is changed
+
+ - name: "Install chart again with same name {{ chart_release_replaced_name }}"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_replaced_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ replace: True
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart is installed from {{ source }}"
+ assert:
+ that:
+ - install is changed
+
+ - name: Remove {{ chart_test }} (cleanup)
+ helm:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ name: "{{ chart_release_replaced_name }}"
+ namespace: "{{ helm_namespace }}"
+ register: install
+
+ - name: Check if chart is removed
+ assert:
+ that:
+ - install is changed
+
+ - name: "Install {{ chart_test }} from {{ source }} with values_files"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ values_files:
+ - "{{ role_path }}/files/values.yaml"
+ register: install
+
+ - name: "Assert that {{ chart_test }} chart has var from {{ source }}"
+ assert:
+ that:
+ - install is changed
+ - install.status.status | lower == 'deployed'
+ - install.status.chart == "{{ chart_test }}-{{ chart_test_version }}"
+ - "install.status['values'].revisionHistoryLimit == 0"
+
+ - name: "Install {{ chart_test }} from {{ source }} with values_files (again)"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ values_files:
+ - "{{ role_path }}/files/values.yaml"
+ register: install
+
+ - name: "Assert the result is consistent"
+ assert:
+ that:
+ - not (install is changed)
+
+ - name: "Remove {{ chart_release_name }} release"
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ namespace: "{{ helm_namespace }}"
+ state: absent
+
+ - name: Render templates
+ helm_template:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ output_dir: "{{ temp_dir }}"
+ values_files:
+ - "{{ role_path }}/files/values.yaml"
+ register: result
+
+ - assert:
+ that:
+ - result is changed
+ - result is not failed
+ - result.rc == 0
+ - result.command is match("{{ helm_binary }} template {{ chart_source }}")
+
+ - name: Check templates created
+ stat:
+ path: "{{ temp_dir }}/{{ chart_test }}/templates"
+ register: result
+
+ - assert:
+ that:
+ result.stat.exists
+
+ - name: Render single template from chart to result
+ helm_template:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ disable_hook: True
+ release_name: "MyRelease"
+ release_namespace: "MyReleaseNamespace"
+ show_only:
+ - "templates/configmap.yaml"
+ release_values:
+ "myValue": "ThisValue"
+ register: result
+ when: chart_source is search("test-chart")
+
+ - assert:
+ that:
+ - result is changed
+ - result is not failed
+ - result.rc == 0
+ - result.command is match("{{ helm_binary }} template MyRelease {{ chart_source }}")
+ - result.stdout is search("ThisValue")
+ when: chart_source is search("test-chart")
+ # limit assertion of test result to controlled (local) chart_source
+
+ - name: Release using non-existent context
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: "{{ chart_release_name }}"
+ chart_ref: "{{ chart_source }}"
+ chart_version: "{{ chart_source_version | default(omit) }}"
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ context: does-not-exist
+ ignore_errors: yes
+ register: result
+
+ - name: Assert that release fails with non-existent context
+ assert:
+ that:
+ - result is failed
+ - "'context \"does-not-exist\" does not exist' in result.stderr"
+
+ always:
+ - name: Clean up temp dir
+ file:
+ state: absent
+ path: "{{ temp_dir }}"
+ ignore_errors: true
+
+ - name: Remove helm namespace
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_local_path.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_local_path.yml
new file mode 100644
index 00000000..db3058e2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_local_path.yml
@@ -0,0 +1,111 @@
+---
+- name: Git clone stable repo
+ git:
+ repo: "{{ chart_test_git_repo }}"
+ dest: /tmp/helm_test_repo
+ version: 631eb8413f6728962439488f48d7d6fbb954a6db
+ depth: 1
+
+- name: Git clone stable repo upgrade
+ git:
+ repo: "{{ chart_test_git_repo }}"
+ dest: /tmp/helm_test_repo_upgrade
+ version: d37b5025ffc8be49699898369fbb59661e2a8ffb
+ depth: 1
+
+- name: Install Chart from local path
+ include_tasks: "../tests_chart.yml"
+ vars:
+ source: local_path
+ chart_test: "{{ chart_test_local_path }}"
+ chart_source: "/tmp/helm_test_repo/stable/{{ chart_test_local_path }}/"
+ chart_source_upgrade: "/tmp/helm_test_repo_upgrade/stable/{{ chart_test_local_path }}/"
+ chart_test_version: "{{ chart_test_version_local_path }}"
+ chart_test_version_upgrade: "{{ chart_test_version_upgrade_local_path }}"
+ chart_name: "local-path-001"
+ helm_namespace: "{{ test_namespace[4] }}"
+
+- name: Test appVersion idempotence
+ vars:
+ chart_test: "test-chart"
+ chart_test_upgrade: "test-chart-v2"
+ chart_test_version: "0.1.0"
+ chart_test_version_upgrade: "0.2.0"
+ chart_test_app_version: "v1"
+ chart_test_upgrade_app_version: "v2"
+ block:
+ - name: Copy test chart
+ copy:
+ src: "{{ chart_test }}"
+ dest: "/tmp/helm_test_appversion/test-chart/"
+
+ - name: Copy test chart v2
+ copy:
+ src: "{{ chart_test_upgrade }}"
+ dest: "/tmp/helm_test_appversion/test-chart/"
+
+ # create package with appVersion v1
+ - name: "Package chart into archive with appVersion {{ chart_test_app_version }}"
+ command: "{{ helm_binary }} package --app-version {{ chart_test_app_version }} /tmp/helm_test_appversion/test-chart/{{ chart_test }}"
+ - name: "Move appVersion {{ chart_test_app_version }} chart archive"
+ copy:
+ remote_src: true
+ src: "test-chart-{{ chart_test_version }}.tgz"
+ dest: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_app_version }}-{{ chart_test_version }}.tgz"
+
+ # create package with appVersion v2
+ - name: "Package chart into archive with appVersion {{ chart_test_upgrade_app_version }}"
+ command: "{{ helm_binary }} package --app-version {{ chart_test_upgrade_app_version }} /tmp/helm_test_appversion/test-chart/{{ chart_test_upgrade }}"
+ - name: "Move appVersion {{ chart_test_upgrade_app_version }} chart archive"
+ copy:
+ remote_src: true
+ src: "test-chart-{{ chart_test_version_upgrade }}.tgz"
+ dest: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_upgrade_app_version }}-{{ chart_test_version_upgrade }}.tgz"
+
+ - name: Install Chart from local path
+ include_tasks: "../tests_chart.yml"
+ vars:
+ source: local_path
+ chart_source: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_app_version }}-{{ chart_test_version }}.tgz"
+ chart_source_upgrade: "/tmp/helm_test_appversion/test-chart/{{ chart_test }}-{{ chart_test_upgrade_app_version }}-{{ chart_test_version_upgrade }}.tgz"
+ chart_name: "local-path-002"
+ helm_namespace: "{{ test_namespace[5] }}"
+
+- name: Test appVersion handling when null
+ vars:
+ chart_test: "appversionless-chart"
+ chart_test_upgrade: "appversionless-chart-v2"
+ chart_test_version: "0.1.0"
+ chart_test_version_upgrade: "0.2.0"
+ block:
+ - name: Copy test chart
+ copy:
+ src: "{{ chart_test }}"
+ dest: "/tmp/helm_test_appversion/test-null/"
+
+ - name: Copy test chart v2
+ copy:
+ src: "{{ chart_test_upgrade }}"
+ dest: "/tmp/helm_test_appversion/test-null/"
+
+ # create package with appVersion v1
+ - name: "Package chart into archive with appVersion v1"
+ command: "{{ helm_binary }} package --app-version v1 /tmp/helm_test_appversion/test-null/{{ chart_test_upgrade }}"
+
+ - name: Install Chart from local path
+ include_tasks: "../tests_chart.yml"
+ vars:
+ source: local_path
+ chart_source: "/tmp/helm_test_appversion/test-null/{{ chart_test }}/"
+ chart_source_upgrade: "{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz"
+ chart_name: "local-path-003"
+ helm_namespace: "{{ test_namespace[6] }}"
+
+- name: Remove clone repos
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - /tmp/helm_test_repo
+ - /tmp/helm_test_repo_upgrade
+ - /tmp/helm_test_appversion
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_repository.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_repository.yml
new file mode 100644
index 00000000..0d8bae4f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_repository.yml
@@ -0,0 +1,22 @@
+---
+- name: Add chart repo
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm
+ repo_url: "{{ chart_test_repo }}"
+
+- name: Install Chart from repository
+ include_tasks: "../tests_chart.yml"
+ vars:
+ source: repository
+ chart_source: "test_helm/{{ chart_test }}"
+ chart_source_version: "{{ chart_test_version }}"
+ chart_source_version_upgrade: "{{ chart_test_version_upgrade }}"
+ helm_namespace: "{{ test_namespace[7] }}"
+
+- name: Remove chart repo
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm
+ repo_url: "{{ chart_test_repo }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_url.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_url.yml
new file mode 100644
index 00000000..fdd839d3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm/tasks/tests_chart/from_url.yml
@@ -0,0 +1,8 @@
+---
+- name: Install Chart from URL
+ include_tasks: "../tests_chart.yml"
+ vars:
+ source: url
+ chart_source: "https://github.com/kubernetes/ingress-nginx/releases/download/helm-chart-{{ chart_test_version }}/{{ chart_test }}-{{ chart_test_version }}.tgz"
+ chart_source_upgrade: "https://github.com/kubernetes/ingress-nginx/releases/download/helm-chart-{{ chart_test_version_upgrade }}/{{ chart_test }}-{{ chart_test_version_upgrade }}.tgz"
+ helm_namespace: "{{ test_namespace[8] }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/aliases
new file mode 100644
index 00000000..f85ad0c8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/aliases
@@ -0,0 +1,3 @@
+time=40
+helm_plugin
+helm \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/defaults/main.yml
new file mode 100644
index 00000000..6c2a691f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
+helm_namespace: helm-diff
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/Chart.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/Chart.yaml
new file mode 100644
index 00000000..5d09a08c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: test-chart
+description: A chart used in molecule tests
+type: application
+version: 0.1.0
+appVersion: "default"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml
new file mode 100644
index 00000000..7ef9931d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-chart-configmap
+data:
+ myValue: {{ default "test" .Values.myValue }}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/meta/main.yml
new file mode 100644
index 00000000..10d989e7
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - remove_namespace
+ - install_helm
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/tasks/main.yml
new file mode 100644
index 00000000..d54d0ba7
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_diff/tasks/main.yml
@@ -0,0 +1,259 @@
+---
+- name: Test helm diff functionality
+ vars:
+ test_chart_ref: "/tmp/test-chart"
+ redis_chart_version: '17.0.5'
+
+ block:
+
+ - name: Install helm diff
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: https://github.com/databus23/helm-diff
+ plugin_version: 3.4.0
+
+ - name: Copy test chart
+ copy:
+ src: "test-chart/"
+ dest: "{{ test_chart_ref }}"
+
+ - name: Install local chart
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ create_namespace: yes
+ register: install
+
+ - assert:
+ that:
+ - install is changed
+
+ - name: Modify local chart
+ blockinfile:
+ create: yes
+ path: "{{ test_chart_ref }}/templates/anothermap.yaml"
+ block: !unsafe |
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: test-chart-another-configmap
+ data:
+ foo: {{ .Values.foo | default "bar" }}
+
+ - name: Test helm diff in check mode
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ check_mode: yes
+ diff: yes
+ register: diff_result
+
+ - name: Check if helm diff check is correct
+ vars:
+ foo_bar_value: "+ foo: bar"
+ assert:
+ that:
+ - foo_bar_value in diff_result.diff.prepared
+
+ - name: Upgrade local chart with modifications
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ register: install
+
+ - assert:
+ that:
+ - install is changed
+
+ - name: No diff in check mode when no change
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ check_mode: yes
+ diff: yes
+ register: diff_result
+
+ - name: Check if no diff in check mode when no change
+ assert:
+ that:
+ - '"diff" not in diff_result'
+
+ - name: Upgrade modified local chart idempotency check
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ register: install
+
+ - assert:
+ that:
+ - install is not changed
+
+ - name: Modify values
+ blockinfile:
+ create: yes
+ path: "{{ test_chart_ref }}/values.yml"
+ block: |
+ ---
+ foo: baz
+
+ - name: Upgrade with values file
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ values_files:
+ - "{{ test_chart_ref }}/values.yml"
+ register: install
+
+ - assert:
+ that:
+ - install is changed
+
+ - name: Upgrade with values file idempotency check
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ values_files:
+ - "{{ test_chart_ref }}/values.yml"
+ register: install
+
+ - assert:
+ that:
+ - install is not changed
+
+ - name: Upgrade with values
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ values:
+ foo: gaz
+ register: install
+
+ - assert:
+ that:
+ - install is changed
+
+ - name: Upgrade with values idempotency check
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: test-chart
+ namespace: "{{ helm_namespace }}"
+ chart_ref: "{{ test_chart_ref }}"
+ values:
+ foo: gaz
+ register: install
+
+ - assert:
+ that:
+ - install is not changed
+
+ # Test helm diff with chart_repo_url
+ - name: Define Redis chart values
+ set_fact:
+ redis_chart_values:
+ commonLabels:
+ phase: testing
+ company: RedHat
+ image:
+ tag: 6.2.6-debian-10-r135
+ architecture: standalone
+
+ - name: Install Redis chart
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ chart_ref: redis
+ namespace: "{{ helm_namespace }}"
+ name: redis-chart
+ chart_version: "{{ redis_chart_version }}"
+ release_values: "{{ redis_chart_values }}"
+
+ - name: Upgrade Redis chart
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ chart_ref: redis
+ namespace: "{{ helm_namespace }}"
+ name: redis-chart
+ chart_version: "{{ redis_chart_version }}"
+ release_values: "{{ redis_chart_values }}"
+ check_mode: yes
+ register: redis_upgrade
+
+ - name: Assert that module raised a warning
+ assert:
+ that:
+ - not redis_upgrade.changed
+ - redis_upgrade.warnings is defined
+ - redis_upgrade.warnings | length == 1
+ - redis_upgrade.warnings[0] == "The default idempotency check can fail to report changes in certain cases. Install helm diff >= 3.4.1 for better results."
+
+ - name: Uninstall helm diff
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: diff
+ ignore_errors: yes
+
+ - name: Install helm diff (version=3.4.1)
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: https://github.com/databus23/helm-diff
+ plugin_version: 3.4.1
+
+ - name: Upgrade Redis chart once again
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ chart_ref: redis
+ namespace: "{{ helm_namespace }}"
+ name: redis-chart
+ chart_version: "{{ redis_chart_version }}"
+ release_values: "{{ redis_chart_values }}"
+ check_mode: yes
+ register: redis_upgrade_2
+
+ - name: Assert that module raised a warning
+ assert:
+ that:
+ - redis_upgrade_2.changed
+ - redis_upgrade_2.warnings is not defined
+
+ always:
+ - name: Remove chart directory
+ file:
+ path: "{{ test_chart_ref }}"
+ state: absent
+ ignore_errors: yes
+
+ - name: Uninstall helm diff
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: diff
+ ignore_errors: yes
+
+ - name: Remove helm namespace
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/aliases
new file mode 100644
index 00000000..5f429d06
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/aliases
@@ -0,0 +1,2 @@
+time=40
+helm \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/defaults/main.yml
new file mode 100644
index 00000000..0e30cb94
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
+default_kubeconfig_path: "~/.kube/config"
+test_namespace:
+ - "helm-in-memory-kubeconfig"
+ - "helm-kubeconfig-with-ca-cert"
+ - "helm-kubeconfig-with-insecure-skip-tls-verify"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/meta/main.yml
new file mode 100644
index 00000000..2e3ba2fa
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_in_memory_kubeconfig.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_in_memory_kubeconfig.yml
new file mode 100644
index 00000000..aebd69c4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_in_memory_kubeconfig.yml
@@ -0,0 +1,9 @@
+---
+- set_fact:
+ custom_config: "{{ lookup('file', default_kubeconfig_path | expanduser) | from_yaml }}"
+
+- name: Test helm modules using in-memory kubeconfig
+ include_tasks: "tests_helm_auth.yml"
+ vars:
+ test_kubeconfig: "{{ custom_config }}"
+ helm_namespace: "{{ test_namespace[0] }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_cacert.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_cacert.yml
new file mode 100644
index 00000000..0af0030a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_cacert.yml
@@ -0,0 +1,76 @@
+---
+- set_fact:
+ content: "{{ lookup('file', default_kubeconfig_path) | from_yaml }}"
+ custom_content: {}
+ clusters: []
+
+- set_fact:
+ custom_content: "{{ custom_content | combine({item.key: item.value}) }}"
+ when: "{{ item.key not in ['clusters'] }}"
+ with_dict: "{{ content }}"
+
+- set_fact:
+ clusters: "{{ clusters + [item | combine({'cluster': {'certificate-authority-data': omit}}, recursive=true)] }}"
+ with_items: "{{ content.clusters }}"
+
+- set_fact:
+ custom_content: "{{ custom_content | combine({'clusters': clusters}) }}"
+
+- name: create temporary file for ca_cert
+ tempfile:
+ suffix: .cacert
+ register: ca_file
+
+- name: copy content into certificate file
+ copy:
+ content: "{{ content.clusters.0.cluster['certificate-authority-data'] | b64decode }}"
+ dest: "{{ ca_file.path }}"
+
+- name: create temporary file to save config in
+ tempfile:
+ suffix: .config
+ register: tfile
+
+- name: create custom config
+ copy:
+ content: "{{ custom_content | to_yaml }}"
+ dest: "{{ tfile.path }}"
+
+- block:
+ - name: Install Redis chart without ca_cert (should fail)
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ chart_ref: redis
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ name: redis-chart
+ chart_version: '17.0.5'
+ release_values:
+ architecture: standalone
+ release_state: present
+ kubeconfig: "{{ tfile.path }}"
+ ignore_errors: true
+ register: _install
+
+ - name: assert task failed
+ assert:
+ that:
+ - _install is failed
+ - '"Error: Kubernetes cluster unreachable" in _install.msg'
+
+ - name: Test helm modules using in-memory kubeconfig
+ include_tasks: "tests_helm_auth.yml"
+ vars:
+ test_kubeconfig: "{{ tfile.path }}"
+ test_ca_cert: "{{ ca_file.path }}"
+
+ vars:
+ helm_namespace: "{{ test_namespace[1] }}"
+
+ always:
+ - name: Delete temporary file
+ file:
+ state: absent
+ path: "{{ tfile.path }}"
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_validate_certs.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_validate_certs.yml
new file mode 100644
index 00000000..73c02a27
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/from_kubeconfig_with_validate_certs.yml
@@ -0,0 +1,67 @@
+---
+- set_fact:
+ content: "{{ lookup('file', default_kubeconfig_path) | from_yaml }}"
+ custom_content: {}
+ clusters: []
+
+- set_fact:
+ custom_content: "{{ custom_content | combine({item.key: item.value}) }}"
+ when: "{{ item.key not in ['clusters'] }}"
+ with_dict: "{{ content }}"
+
+- set_fact:
+ clusters: "{{ clusters + [item | combine({'cluster': {'certificate-authority-data': omit}}, recursive=true)] }}"
+ with_items: "{{ content.clusters }}"
+
+- set_fact:
+ custom_content: "{{ custom_content | combine({'clusters': clusters}) }}"
+
+- name: create temporary file to save config in
+ tempfile:
+ suffix: .config
+ register: tfile
+
+- name: create custom config
+ copy:
+ content: "{{ custom_content | to_yaml }}"
+ dest: "{{ tfile.path }}"
+
+- block:
+ - name: Install Redis chart without validate_certs=false (should fail)
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ chart_ref: redis
+ namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ name: redis-chart
+ chart_version: '17.0.5'
+ release_values:
+ architecture: standalone
+ release_state: present
+ kubeconfig: "{{ tfile.path }}"
+ validate_certs: true
+ ignore_errors: true
+ register: _install
+
+ - name: assert task failed
+ assert:
+ that:
+ - _install is failed
+ - '"Error: Kubernetes cluster unreachable" in _install.msg'
+
+ - name: Test helm modules using in-memory kubeconfig
+ include_tasks: "tests_helm_auth.yml"
+ vars:
+ test_kubeconfig: "{{ tfile.path }}"
+ test_validate_certs: false
+
+ vars:
+ helm_namespace: "{{ test_namespace[2] }}"
+
+ always:
+ - name: Delete temporary file
+ file:
+ state: absent
+ path: "{{ tfile.path }}"
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/main.yml
new file mode 100644
index 00000000..244f1bdf
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/main.yml
@@ -0,0 +1,19 @@
+---
+- name: Test helm with in-memory kubeconfig
+ include_tasks: "from_in_memory_kubeconfig.yml"
+
+- name: Test helm with custom kubeconfig and validate_certs=false
+ include_tasks: "from_kubeconfig_with_validate_certs.yml"
+ loop_control:
+ loop_var: test_helm_version
+ with_items:
+ - "v3.10.3"
+ - "v3.8.2"
+
+- name: Test helm with custom kubeconfig and ca_cert
+ include_tasks: "from_kubeconfig_with_cacert.yml"
+ loop_control:
+ loop_var: test_helm_version
+ with_items:
+ - "v3.5.1"
+ - "v3.4.2"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/tests_helm_auth.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/tests_helm_auth.yml
new file mode 100644
index 00000000..8599b66a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_kubeconfig/tasks/tests_helm_auth.yml
@@ -0,0 +1,197 @@
+---
+- name: create temporary directory
+ tempfile:
+ state: directory
+ suffix: .helm
+ register: _dir
+
+- name: Install helm binary
+ block:
+ - name: "Install {{ test_helm_version }}"
+ include_role:
+ name: install_helm
+ vars:
+ helm_version: "{{ test_helm_version }}"
+
+ when: test_helm_version is defined
+
+- set_fact:
+ saved_kubeconfig_path: "{{ _dir.path }}/config"
+
+- block:
+ - name: Copy default kubeconfig
+ copy:
+ remote_src: true
+ src: "{{ default_kubeconfig_path }}"
+ dest: "{{ saved_kubeconfig_path }}"
+
+ - name: Delete default kubeconfig
+ file:
+ path: "{{ default_kubeconfig_path }}"
+ state: absent
+
+ # helm_plugin and helm_plugin_info
+ - name: Install subenv plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ state: present
+ plugin_path: https://github.com/hydeenoble/helm-subenv
+ register: plugin
+
+ - assert:
+ that:
+ - plugin is changed
+
+ - name: Gather info about all plugin
+ helm_plugin_info:
+ binary_path: "{{ helm_binary }}"
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ register: plugin_info
+
+ - assert:
+ that:
+ - '"plugin_list" in plugin_info'
+ - plugin_info.plugin_list != []
+
+ # helm_repository, helm, helm_info
+ - name: Add test_bitnami chart repository
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_bitnami
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ repo_url: https://charts.bitnami.com/bitnami
+ register: repository
+
+ - name: Assert that repository was added
+ assert:
+ that:
+ - repository is changed
+
+ - name: Install chart from repository added before
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: rabbitmq
+ chart_ref: test_bitnami/rabbitmq
+ namespace: "{{ helm_namespace }}"
+ update_repo_cache: true
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ create_namespace: true
+ register: deploy
+
+ - name: Assert chart was successfully deployed
+ assert:
+ that:
+ - deploy is changed
+
+ - name: Get chart content
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ name: "rabbitmq"
+ namespace: "{{ helm_namespace }}"
+ register: chart_info
+
+ - name: Assert chart was successfully deployed
+ assert:
+ that:
+ - '"status" in chart_info'
+ - chart_info.status.status is defined
+ - chart_info.status.status == "deployed"
+
+ - name: Remove chart
+ helm:
+ binary_path: "{{ helm_binary }}"
+ name: rabbitmq
+ namespace: "{{ helm_namespace }}"
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ state: absent
+ register: remove_chart
+
+ - name: Assert chart was successfully removed
+ assert:
+ that:
+ - remove_chart is changed
+
+ - name: Get chart content
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ name: "rabbitmq"
+ namespace: "{{ helm_namespace }}"
+ register: chart_info
+
+ - name: Assert chart was successfully deployed
+ assert:
+ that:
+ - '"status" not in chart_info'
+
+ - name: Remove chart repository
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_bitnami
+ kubeconfig: "{{ test_kubeconfig | default(omit) }}"
+ validate_certs: "{{ test_validate_certs | default(omit) }}"
+ ca_cert: "{{ test_ca_cert | default(omit) }}"
+ state: absent
+ register: remove
+
+ - name: Assert that repository was removed
+ assert:
+ that:
+ - remove is changed
+
+ always:
+ - name: Return kubeconfig
+ copy:
+ remote_src: true
+ src: "{{ saved_kubeconfig_path }}"
+ dest: "{{ default_kubeconfig_path }}"
+ ignore_errors: true
+
+ - name: Delete temporary directory
+ file:
+ path: "{{ _dir.path }}"
+ state: absent
+ ignore_errors: true
+
+ - name: Delete temporary directory for helm install
+ file:
+ path: "{{ _helm_install.path }}"
+ state: absent
+ ignore_errors: true
+ when: _helm_install is defined
+
+ - name: Remove subenv plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ plugin_name: subenv
+ state: absent
+ ignore_errors: true
+
+ - name: Delete namespace
+ k8s:
+ kind: Namespace
+ name: "{{ helm_namespace }}"
+ ignore_errors: true
+
+ - name: Delete helm repository
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_bitnami
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/aliases
new file mode 100644
index 00000000..babf73f4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/aliases
@@ -0,0 +1,3 @@
+time=30
+helm_plugin
+helm_plugin_info \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/defaults/main.yml
new file mode 100644
index 00000000..60d628d5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/files/sample_plugin/plugin.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/files/sample_plugin/plugin.yaml
new file mode 100644
index 00000000..8703bc29
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/files/sample_plugin/plugin.yaml
@@ -0,0 +1,11 @@
+name: "sample_plugin"
+version: "0.0.1"
+usage: "Sample Helm Plugin"
+description: |-
+ This plugin provides sample plugin to Helm.
+ usage:
+ This is new line
+ This is another line
+ignoreFlags: false
+useTunnel: false
+command: "$HELM_PLUGIN_DIR/main.sh"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/meta/main.yml
new file mode 100644
index 00000000..cf4590de
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - install_helm
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/tasks/main.yml
new file mode 100644
index 00000000..72f0d7a3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_plugin/tasks/main.yml
@@ -0,0 +1,165 @@
+---
+- name: Install env plugin in check mode
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: https://github.com/adamreese/helm-env
+ register: check_install_env
+ check_mode: true
+
+- assert:
+ that:
+ - check_install_env.changed
+
+- name: Install env plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: https://github.com/adamreese/helm-env
+ register: install_env
+
+- assert:
+ that:
+ - install_env.changed
+
+- name: Gather info about all plugin
+ helm_plugin_info:
+ binary_path: "{{ helm_binary }}"
+ register: plugin_info
+
+- assert:
+ that:
+ - plugin_info.plugin_list is defined
+
+- name: Install env plugin again
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: https://github.com/adamreese/helm-env
+ register: install_env
+
+- assert:
+ that:
+ - not install_env.changed
+
+- name: Uninstall env plugin in check mode
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: env
+ register: check_uninstall_env
+ check_mode: true
+
+- assert:
+ that:
+ - check_uninstall_env.changed
+
+- name: Uninstall env plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: env
+ register: uninstall_env
+
+- assert:
+ that:
+ - uninstall_env.changed
+
+- name: Uninstall env plugin again
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: env
+ register: uninstall_env
+
+- assert:
+ that:
+ - not uninstall_env.changed
+
+# https://github.com/ansible-collections/community.kubernetes/issues/399
+- block:
+ - name: Copy required plugin files
+ copy:
+ src: "files/sample_plugin"
+ dest: "/tmp/helm_plugin_test/"
+
+ - name: Install sample_plugin from the directory
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: present
+ plugin_path: "/tmp/helm_plugin_test/sample_plugin"
+ register: sample_plugin_output
+
+ - name: Assert that sample_plugin is installed or not
+ assert:
+ that:
+ - sample_plugin_output.changed
+
+ - name: Gather Helm plugin info
+ helm_plugin_info:
+ binary_path: "{{ helm_binary }}"
+ register: r
+
+ - name: Set sample_plugin version
+ set_fact:
+ plugin_version: "{{ ( r.plugin_list | selectattr('name', 'equalto', plugin_name) | list )[0].version }}"
+ vars:
+ plugin_name: "sample_plugin"
+
+ - name: Assert if sample_plugin with multiline comment is installed
+ assert:
+ that:
+ - plugin_version == "0.0.1"
+ always:
+ - name: Uninstall sample_plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: sample_plugin
+ ignore_errors: yes
+
+- block:
+ - name: uninstall helm plugin secrets
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ plugin_name: secrets
+ state: absent
+
+ - name: install helm-secrets on a specific version
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ plugin_path: https://github.com/jkroepke/helm-secrets
+ plugin_version: 3.4.1
+ state: present
+
+ - name: list helm plugin
+ helm_plugin_info:
+ plugin_name: secrets
+ binary_path: "{{ helm_binary }}"
+ register: plugin_list
+
+ - name: assert that secrets has been installed with specified version
+ assert:
+ that:
+ - plugin_list.plugin_list[0].version == "3.4.1"
+
+ - name: Update helm plugin version to latest
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ plugin_name: secrets
+ state: latest
+ register: _update
+
+ - name: assert update was performed
+ assert:
+ that:
+ - _update.changed
+ - '"Updated plugin: secrets" in _update.stdout'
+
+ always:
+ - name: Uninstall sample_plugin
+ helm_plugin:
+ binary_path: "{{ helm_binary }}"
+ state: absent
+ plugin_name: secrets
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/aliases
new file mode 100644
index 00000000..49fdb76b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/aliases
@@ -0,0 +1,2 @@
+time=27
+helm_pull \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/tasks/main.yml
new file mode 100644
index 00000000..3705c5cb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_pull/tasks/main.yml
@@ -0,0 +1,232 @@
+---
+- name: Define helm versions to test
+ set_fact:
+ helm_versions:
+ - 3.8.0
+ - 3.1.0
+ - 3.0.0
+ - 2.3.0
+
+- block:
+ - name: Create temp directory for helm tests
+ tempfile:
+ state: directory
+ register: tmpdir
+
+ - name: Set temp directory fact
+ set_fact:
+ temp_dir: "{{ tmpdir.path }}"
+
+ - set_fact:
+ destination: "{{ temp_dir }}"
+
+ - name: Create Helm directories
+ file:
+ state: directory
+ path: "{{ temp_dir }}/{{ item }}"
+ with_items: "{{ helm_versions }}"
+
+ - name: Unarchive Helm binary
+ unarchive:
+ src: "https://get.helm.sh/helm-v{{ item }}-linux-amd64.tar.gz"
+ dest: "{{ temp_dir }}/{{ item }}"
+ remote_src: yes
+ with_items: "{{ helm_versions }}"
+
+ # Testing helm pull with helm version == 2.3.0
+ - block:
+ - name: Assert that helm pull failed with helm <= 3.0.0
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ ignore_errors: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is failed
+ - _result.msg == "This module requires helm >= 3.0.0, current version is 2.3.0"
+
+ vars:
+ helm_path: "{{ temp_dir }}/2.3.0/linux-amd64/helm"
+
+ # Testing helm pull with helm version == 3.0.0
+ - block:
+ - name: Download chart using chart_ssl_cert_file
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ chart_ssl_cert_file: ssl_cert_file
+ ignore_errors: true
+ check_mode: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is failed
+ - _result.msg == "Parameter chart_ssl_cert_file requires helm >= 3.1.0, current version is 3.0.0"
+
+ - name: Download chart using chart_ssl_key_file
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ chart_ssl_key_file: ssl_key_file
+ ignore_errors: true
+ check_mode: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is failed
+ - _result.msg == "Parameter chart_ssl_key_file requires helm >= 3.1.0, current version is 3.0.0"
+
+ - name: Download chart using chart_ca_cert
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ chart_ca_cert: ca_cert_file
+ ignore_errors: true
+ check_mode: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is failed
+ - _result.msg == "Parameter chart_ca_cert requires helm >= 3.1.0, current version is 3.0.0"
+
+ vars:
+ helm_path: "{{ temp_dir }}/3.0.0/linux-amd64/helm"
+
+ # Testing helm pull with helm version == 3.1.0
+ - block:
+ - name: Download chart using chart_ssl_cert_file, chart_ca_cert, chart_ssl_key_file
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ chart_ssl_cert_file: ssl_cert_file
+ chart_ssl_key_file: ssl_key_file
+ chart_ca_cert: ca_cert_file
+ check_mode: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is changed
+ - '"--ca-file ca_cert_file" in _result.command'
+ - '"--cert-file ssl_cert_file" in _result.command'
+ - '"--key-file ssl_key_file" in _result.command'
+
+ - name: Download chart using skip_tls_certs_check
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ skip_tls_certs_check: true
+ ignore_errors: true
+ check_mode: true
+ register: _result
+
+ - name: assert that module failed with proper message
+ assert:
+ that:
+ - _result is failed
+ - _result.msg == "Parameter skip_tls_certs_check requires helm >= 3.3.0, current version is 3.1.0"
+
+ vars:
+ helm_path: "{{ temp_dir }}/3.1.0/linux-amd64/helm"
+
+ # Testing helm pull with helm version == 3.8.0
+ - block:
+ # Test options chart_version, verify, pass-credentials, provenance, untar_chart
+ # skip_tls_certs_check, repo_url, repo_username, repo_password
+ - name: Testing chart version
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: redis
+ destination: "{{ destination }}"
+ chart_version: "0.2.1"
+ verify_chart: true
+ pass_credentials: true
+ provenance: true
+ untar_chart: true
+ skip_tls_certs_check: true
+ repo_url: "https://charts.bitnami.com/bitnami"
+ repo_username: ansible
+ repo_password: testing123
+ verify_chart_keyring: pubring.gpg
+ check_mode: true
+ register: _result
+
+ - assert:
+ that:
+ - _result is changed
+ - '"--version 0.2.1" in _result.command'
+ - '"--verify" in _result.command'
+ - '"--pass-credentials" in _result.command'
+ - '"--prov" in _result.command'
+ - '"--untar" in _result.command'
+ - '"--insecure-skip-tls-verify" in _result.command'
+ - '"--repo https://charts.bitnami.com/bitnami" in _result.command'
+ - '"--username ansible" in _result.command'
+ - '"--password ***" in _result.command'
+ - '"--keyring pubring.gpg" in _result.command'
+
+ - name: Download chart using chart_ref
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: https://github.com/grafana/helm-charts/releases/download/grafana-5.6.0/grafana-5.6.0.tgz
+ destination: "{{ destination }}"
+ register: _result
+
+ - name: Check chart on local filesystem
+ stat:
+ path: "{{ destination }}/grafana-5.6.0.tgz"
+ register: _chart
+
+ - name: Validate that chart was downloaded
+ assert:
+ that:
+ - _result is changed
+ - _chart.stat.exists
+ - _chart.stat.isreg
+
+ - name: Download chart using untar_chart
+ helm_pull:
+ binary_path: "{{ helm_path }}"
+ chart_ref: redis
+ destination: "{{ destination }}"
+ repo_url: "https://charts.bitnami.com/bitnami"
+ untar_chart: true
+ register: _result
+
+ - name: Check chart on local filesystem
+ stat:
+ path: "{{ destination }}/redis"
+ register: _chart
+
+ - name: Validate that chart was downloaded
+ assert:
+ that:
+ - _result is changed
+ - _chart.stat.exists
+ - _chart.stat.isdir
+
+ vars:
+ helm_path: "{{ temp_dir }}/3.8.0/linux-amd64/helm"
+
+
+ always:
+ - name: Delete temp directory
+ file:
+ state: absent
+ path: "{{ temp_dir }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/aliases
new file mode 100644
index 00000000..eb25b7b3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/aliases
@@ -0,0 +1,5 @@
+time=20
+helm_repository
+helm_info
+helm
+helm_template \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/defaults/main.yml
new file mode 100644
index 00000000..5b60b517
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+chart_test_repo: "https://kubernetes.github.io/ingress-nginx"
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/meta/main.yml
new file mode 100644
index 00000000..cf4590de
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - install_helm
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/tasks/main.yml
new file mode 100644
index 00000000..dfd649fe
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_repository/tasks/main.yml
@@ -0,0 +1,80 @@
+---
+- 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
+
+- name: Assert that test_helm_repo repository is added
+ assert:
+ that:
+ - repository is changed
+
+- name: Check idempotency
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm_repo
+ repo_url: "{{ chart_test_repo }}"
+ register: repository
+
+- name: Assert idempotency
+ assert:
+ that:
+ - repository is not changed
+
+- 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
+ ignore_errors: yes
+
+- name: Assert that adding repository with the same name failed
+ assert:
+ that:
+ - repository_errors is failed
+
+- name: Succesfully add repository with the same name when forcing
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm_repo
+ repo_url: "{{ chart_test_repo }}"
+ force: true
+ register: repository
+
+- name: Assert that test_helm_repo repository is changed
+ assert:
+ that:
+ - repository is changed
+
+- name: Remove test_helm_repo chart repository
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm_repo
+ state: absent
+ register: repository
+
+- name: Assert that test_helm_repo repository is removed
+ assert:
+ that:
+ - repository is changed
+
+- name: Check idempotency after remove
+ helm_repository:
+ binary_path: "{{ helm_binary }}"
+ name: test_helm_repo
+ state: absent
+ register: repository
+
+- name: Assert idempotency
+ assert:
+ that:
+ - repository is not changed
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/aliases
new file mode 100644
index 00000000..c042c5b4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/aliases
@@ -0,0 +1,3 @@
+time=40
+helm
+helm_info \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/defaults/main.yml
new file mode 100644
index 00000000..73c5a0bb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+helm_binary: "/tmp/helm/{{ ansible_system | lower }}-amd64/helm"
+helm_namespace: helm-set-values
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/meta/main.yml
new file mode 100644
index 00000000..2e3ba2fa
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/tasks/main.yml
new file mode 100644
index 00000000..7ce5125f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/helm_set_values/tasks/main.yml
@@ -0,0 +1,109 @@
+- name: Install helm using set_values parameters
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: mariadb
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ release_name: test-mariadb
+ release_namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ set_values:
+ - value: phase=integration
+ value_type: string
+ - value: versioned=false
+
+- name: Get value set as string
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ release_name: test-mariadb
+ release_namespace: "{{ helm_namespace }}"
+ register: user_values
+
+- name: Assert that release was created with user-defined variables
+ assert:
+ that:
+ - '"phase" in user_values.status["values"]'
+ - '"versioned" in user_values.status["values"]'
+ - user_values.status["values"]["phase"] == "integration"
+ - user_values.status["values"]["versioned"] is false
+
+# install chart using set_values and release_values
+- name: Install helm binary (> 3.10.0) requires to use set-json
+ include_role:
+ name: install_helm
+ vars:
+ helm_version: "v3.10.3"
+
+- name: Install helm using set_values parameters
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: apache
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ release_name: test-apache
+ release_namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ set_values:
+ - value: 'master.image={"registry": "docker.io", "repository": "bitnami/apache", "tag": "2.4.54-debian-11-r74"}'
+ value_type: json
+ release_values:
+ replicaCount: 3
+
+- name: Get release info
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ release_name: test-apache
+ release_namespace: "{{ helm_namespace }}"
+ register: values
+
+- name: Assert that release was created with user-defined variables
+ assert:
+ that:
+ - values.status["values"].replicaCount == 3
+ - values.status["values"].master.image.registry == "docker.io"
+ - values.status["values"].master.image.repository == "bitnami/apache"
+ - values.status["values"].master.image.tag == "2.4.54-debian-11-r74"
+
+# install chart using set_values and values_files
+- name: create temporary file to save values in
+ tempfile:
+ suffix: .yml
+ register: ymlfile
+
+- block:
+ - name: copy content into values file
+ copy:
+ content: |
+ ---
+ mode: distributed
+ dest: "{{ ymlfile.path }}"
+
+ - name: Install helm using set_values parameters
+ helm:
+ binary_path: "{{ helm_binary }}"
+ chart_ref: minio
+ chart_repo_url: https://charts.bitnami.com/bitnami
+ release_name: test-minio
+ release_namespace: "{{ helm_namespace }}"
+ create_namespace: true
+ set_values:
+ - value: 'disableWebUI=true'
+ values_files:
+ - "{{ ymlfile.path }}"
+
+ - name: Get release info
+ helm_info:
+ binary_path: "{{ helm_binary }}"
+ release_name: test-minio
+ release_namespace: "{{ helm_namespace }}"
+ register: values
+
+ - name: Assert that release was created with user-defined variables
+ assert:
+ that:
+ - values.status["values"].mode == "distributed"
+ - values.status["values"].disableWebUI is true
+
+ always:
+ - name: Delete temporary file
+ file:
+ state: absent
+ path: "{{ ymlfile.path }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/aliases
new file mode 100644
index 00000000..7a68b11d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/aliases
@@ -0,0 +1 @@
+disabled
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/defaults/main.yml
new file mode 100644
index 00000000..0f8c952f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/defaults/main.yml
@@ -0,0 +1,4 @@
+---
+helm_version: v3.7.0
+helm_install_path: /tmp/helm
+helm_default_archive_name: "helm-{{ helm_version }}-{{ ansible_system | lower }}-amd64.tar.gz"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/tasks/main.yml
new file mode 100644
index 00000000..49e36a46
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/install_helm/tasks/main.yml
@@ -0,0 +1,15 @@
+---
+- name: Init Helm folders
+ file:
+ path: "{{ helm_install_path }}"
+ state: directory
+
+- name: Unarchive Helm binary
+ unarchive:
+ src: "https://get.helm.sh/{{ helm_archive_name | default(helm_default_archive_name) }}"
+ dest: "{{ helm_install_path }}"
+ remote_src: yes
+ retries: 10
+ delay: 5
+ register: result
+ until: result is not failed
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/aliases
new file mode 100644
index 00000000..c023328e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/aliases
@@ -0,0 +1,3 @@
+context/target
+time=42
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/create_resources.yml b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/create_resources.yml
new file mode 100644
index 00000000..7e16f942
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/create_resources.yml
@@ -0,0 +1,46 @@
+---
+- name: Create inventory files
+ hosts: localhost
+ gather_facts: false
+
+ collections:
+ - kubernetes.core
+
+ roles:
+ - role: setup_kubeconfig
+ kubeconfig_operation: 'save'
+
+ tasks:
+ - name: Create inventory files
+ copy:
+ content: "{{ item.content }}"
+ dest: "{{ item.path }}"
+ vars:
+ hostname: "{{ lookup('file', user_credentials_dir + '/host_data.txt') }}"
+ test_cert_file: "{{ user_credentials_dir | realpath + '/cert_file_data.txt' }}"
+ test_key_file: "{{ user_credentials_dir | realpath + '/key_file_data.txt' }}"
+ test_ca_cert: "{{ user_credentials_dir | realpath + '/ssl_ca_cert_data.txt' }}"
+ with_items:
+ - path: "test_inventory_aliases_with_ssl_k8s.yml"
+ content: |
+ ---
+ plugin: kubernetes.core.k8s
+ connections:
+ - namespaces:
+ - inventory
+ host: "{{ hostname }}"
+ cert_file: "{{ test_cert_file }}"
+ key_file: "{{ test_key_file }}"
+ verify_ssl: true
+ ssl_ca_cert: "{{ test_ca_cert }}"
+ - path: "test_inventory_aliases_no_ssl_k8s.yml"
+ content: |
+ ---
+ plugin: kubernetes.core.k8s
+ connections:
+ - namespaces:
+ - inventory
+ host: "{{ hostname }}"
+ cert_file: "{{ test_cert_file }}"
+ key_file: "{{ test_key_file }}"
+ verify_ssl: false
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/delete_resources.yml b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/delete_resources.yml
new file mode 100644
index 00000000..ec09ec5c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/delete_resources.yml
@@ -0,0 +1,30 @@
+---
+- name: Delete inventory namespace
+ hosts: localhost
+ connection: local
+ gather_facts: true
+
+ roles:
+ - role: setup_kubeconfig
+ kubeconfig_operation: 'revert'
+
+ tasks:
+ - name: Delete temporary files
+ file:
+ state: absent
+ path: "{{ user_credentials_dir ~ '/' ~ item }}"
+ ignore_errors: true
+ with_items:
+ - test_inventory_aliases_with_ssl_k8s.yml
+ - test_inventory_aliases_no_ssl_k8s.yml
+ - ssl_ca_cert_data.txt
+ - key_file_data.txt
+ - cert_file_data.txt
+ - host_data.txt
+
+ - name: Remove inventory namespace
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: inventory
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/play.yml b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/play.yml
new file mode 100644
index 00000000..07baf1a3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/play.yml
@@ -0,0 +1,90 @@
+---
+- name: Converge
+ hosts: localhost
+ connection: local
+
+ collections:
+ - kubernetes.core
+
+ vars_files:
+ - vars/main.yml
+
+ tasks:
+ - name: Delete existing namespace
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: inventory
+ wait: yes
+ state: absent
+
+ - name: Ensure namespace exists
+ k8s:
+ api_version: v1
+ kind: Namespace
+ name: inventory
+
+ - name: Add a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: inventory
+ namespace: inventory
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: 400
+ vars:
+ k8s_pod_name: inventory
+ k8s_pod_image: python
+ k8s_pod_command:
+ - python
+ - '-m'
+ - http.server
+ k8s_pod_env:
+ - name: TEST
+ value: test
+
+ - meta: refresh_inventory
+
+- name: Verify inventory and connection plugins
+ hosts: namespace_inventory_pods
+ gather_facts: no
+
+ vars:
+ file_content: |
+ Hello world
+
+ tasks:
+ - name: End play if host not running (TODO should we not add these to the inventory?)
+ meta: end_host
+ when: pod_phase != "Running"
+
+ - debug: var=hostvars
+ - setup:
+
+ - debug: var=ansible_facts
+
+ - name: Assert the TEST environment variable was retrieved
+ assert:
+ that: ansible_facts.env.TEST == 'test'
+
+ - name: Copy a file into the host
+ copy:
+ content: '{{ file_content }}'
+ dest: /tmp/test_file
+
+ - name: Retrieve the file from the host
+ slurp:
+ src: /tmp/test_file
+ register: slurped_file
+
+ - name: Assert the file content matches expectations
+ assert:
+ that: (slurped_file.content|b64decode) == file_content
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/test.inventory_k8s.yml b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/test.inventory_k8s.yml
new file mode 100644
index 00000000..cdbb9316
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/test.inventory_k8s.yml
@@ -0,0 +1,2 @@
+---
+plugin: kubernetes.core.k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/vars/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/vars/main.yml
new file mode 100644
index 00000000..5656784f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/playbooks/vars/main.yml
@@ -0,0 +1,38 @@
+---
+k8s_pod_metadata:
+ labels:
+ app: "{{ k8s_pod_name }}"
+
+k8s_pod_spec:
+ serviceAccount: "{{ k8s_pod_service_account }}"
+ containers:
+ - image: "{{ k8s_pod_image }}"
+ imagePullPolicy: Always
+ name: "{{ k8s_pod_name }}"
+ command: "{{ k8s_pod_command }}"
+ readinessProbe:
+ initialDelaySeconds: 15
+ exec:
+ command:
+ - /bin/true
+ resources: "{{ k8s_pod_resources }}"
+ ports: "{{ k8s_pod_ports }}"
+ env: "{{ k8s_pod_env }}"
+
+
+k8s_pod_service_account: default
+
+k8s_pod_resources:
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
+
+k8s_pod_command: []
+
+k8s_pod_ports: []
+
+k8s_pod_env: []
+
+k8s_pod_template:
+ metadata: "{{ k8s_pod_metadata }}"
+ spec: "{{ k8s_pod_spec }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/runme.sh b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/runme.sh
new file mode 100755
index 00000000..4548f4bb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/inventory_k8s/runme.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -eux
+
+export ANSIBLE_ROLES_PATH="../"
+USER_CREDENTIALS_DIR=$(pwd)
+
+ansible-playbook playbooks/delete_resources.yml -e "user_credentials_dir=${USER_CREDENTIALS_DIR}" "$@"
+
+{
+export ANSIBLE_INVENTORY_ENABLED=kubernetes.core.k8s,yaml
+export ANSIBLE_PYTHON_INTERPRETER=auto_silent
+
+ansible-playbook playbooks/play.yml -i playbooks/test.inventory_k8s.yml "$@" &&
+
+ansible-playbook playbooks/create_resources.yml -e "user_credentials_dir=${USER_CREDENTIALS_DIR}" "$@" &&
+
+ansible-inventory -i playbooks/test_inventory_aliases_with_ssl_k8s.yml --list "$@" &&
+
+ansible-inventory -i playbooks/test_inventory_aliases_no_ssl_k8s.yml --list "$@" &&
+
+unset ANSIBLE_INVENTORY_ENABLED &&
+
+ansible-playbook playbooks/delete_resources.yml -e "user_credentials_dir=${USER_CREDENTIALS_DIR}" "$@"
+
+} || {
+ ansible-playbook playbooks/delete_resources.yml -e "user_credentials_dir=${USER_CREDENTIALS_DIR}" "$@"
+ exit 1
+} \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/aliases
new file mode 100644
index 00000000..6193ad83
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/aliases
@@ -0,0 +1,2 @@
+time=7
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/tasks/main.yml
new file mode 100644
index 00000000..78d6d567
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_access_review/tasks/main.yml
@@ -0,0 +1,22 @@
+---
+- name: Create a SelfSubjectAccessReview resource
+ register: can_i_create_namespaces
+ ignore_errors: yes
+ k8s:
+ state: present
+ definition:
+ apiVersion: authorization.k8s.io/v1
+ kind: SelfSubjectAccessReview
+ spec:
+ resourceAttributes:
+ group: v1
+ resource: Namespace
+ verb: create
+
+- name: Assert that the SelfSubjectAccessReview request succeded
+ assert:
+ that:
+ - can_i_create_namespaces is successful
+ - can_i_create_namespaces.result.status is defined
+ - can_i_create_namespaces.result.status.allowed is defined
+ - can_i_create_namespaces.result.status.allowed
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/aliases
new file mode 100644
index 00000000..f78aec22
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/aliases
@@ -0,0 +1,2 @@
+time=14
+k8s \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/defaults/main.yml
new file mode 100644
index 00000000..9fc0c0b5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "append-hash"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/meta/main.yml
new file mode 100644
index 00000000..0cb0a524
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/tasks/main.yml
new file mode 100644
index 00000000..3a6411e8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_append_hash/tasks/main.yml
@@ -0,0 +1,69 @@
+---
+- block:
+ - name: Ensure that append_hash namespace exists
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+
+ - name: Create k8s_resource variable
+ set_fact:
+ k8s_resource:
+ metadata:
+ name: config-map-test
+ namespace: "{{ test_namespace }}"
+ apiVersion: v1
+ kind: ConfigMap
+ data:
+ hello: world
+
+ - name: Create config map
+ k8s:
+ definition: "{{ k8s_resource }}"
+ append_hash: yes
+ register: k8s_configmap1
+
+ - name: Check configmap is created with a hash
+ assert:
+ that:
+ - k8s_configmap1 is changed
+ - k8s_configmap1.result.metadata.name != 'config-map-test'
+ - k8s_configmap1.result.metadata.name[:-10] == 'config-map-test-'
+
+ - name: Recreate same config map
+ k8s:
+ definition: "{{ k8s_resource }}"
+ append_hash: yes
+ register: k8s_configmap2
+
+ - name: Check configmaps are different
+ assert:
+ that:
+ - k8s_configmap2 is not changed
+ - k8s_configmap1.result.metadata.name == k8s_configmap2.result.metadata.name
+
+ - name: Add key to config map
+ k8s:
+ definition:
+ metadata:
+ name: config-map-test
+ namespace: "{{ test_namespace }}"
+ apiVersion: v1
+ kind: ConfigMap
+ data:
+ hello: world
+ another: value
+ append_hash: yes
+ register: k8s_configmap3
+
+ - name: Check configmaps are different
+ assert:
+ that:
+ - k8s_configmap3 is changed
+ - k8s_configmap1.result.metadata.name != k8s_configmap3.result.metadata.name
+
+ always:
+ - name: Ensure that namespace is removed
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/aliases
new file mode 100644
index 00000000..ac91dd8f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/aliases
@@ -0,0 +1,4 @@
+slow
+k8s_service
+k8s
+time=9m
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/defaults/main.yml
new file mode 100644
index 00000000..c45a18d8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/defaults/main.yml
@@ -0,0 +1,42 @@
+---
+k8s_pod_metadata:
+ labels:
+ app: "{{ k8s_pod_name }}"
+
+k8s_pod_spec:
+ serviceAccount: "{{ k8s_pod_service_account }}"
+ containers:
+ - image: "{{ k8s_pod_image }}"
+ imagePullPolicy: Always
+ name: "{{ k8s_pod_name }}"
+ command: "{{ k8s_pod_command }}"
+ readinessProbe:
+ initialDelaySeconds: 15
+ exec:
+ command:
+ - /bin/true
+ resources: "{{ k8s_pod_resources }}"
+ ports: "{{ k8s_pod_ports }}"
+ env: "{{ k8s_pod_env }}"
+
+
+k8s_pod_service_account: default
+
+k8s_pod_resources:
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
+
+k8s_pod_command: []
+
+k8s_pod_ports: []
+
+k8s_pod_env: []
+
+k8s_pod_template:
+ metadata: "{{ k8s_pod_metadata }}"
+ spec: "{{ k8s_pod_spec }}"
+
+test_namespace: "apply"
+
+k8s_wait_timeout: 240
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/meta/main.yml
new file mode 100644
index 00000000..0cb0a524
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/main.yml
new file mode 100644
index 00000000..c759d7bc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/main.yml
@@ -0,0 +1,783 @@
+---
+- block:
+ - name: Ensure namespace exists
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ test_namespace }}"
+
+ - name: Add a configmap
+ k8s:
+ name: "apply-configmap"
+ namespace: "{{ test_namespace }}"
+ definition:
+ kind: ConfigMap
+ apiVersion: v1
+ data:
+ one: "1"
+ two: "2"
+ three: "3"
+ apply: yes
+ register: k8s_configmap
+
+ - name: Check configmap was created
+ assert:
+ that:
+ - k8s_configmap is changed
+ - k8s_configmap.result.metadata.annotations|default(False)
+
+ - name: Add same configmap again
+ k8s:
+ definition:
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: "apply-configmap"
+ namespace: "{{ test_namespace }}"
+ data:
+ one: "1"
+ two: "2"
+ three: "3"
+ apply: yes
+ register: k8s_configmap_2
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_configmap_2 is not changed
+
+ - name: Add same configmap again with check mode on
+ k8s:
+ definition:
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: "apply-configmap"
+ namespace: "{{ test_namespace }}"
+ data:
+ one: "1"
+ two: "2"
+ three: "3"
+ apply: yes
+ check_mode: yes
+ register: k8s_configmap_check
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_configmap_check is not changed
+
+ - name: Add same configmap again but using name and namespace args
+ k8s:
+ name: "apply-configmap"
+ namespace: "{{ test_namespace }}"
+ definition:
+ kind: ConfigMap
+ apiVersion: v1
+ data:
+ one: "1"
+ two: "2"
+ three: "3"
+ apply: yes
+ register: k8s_configmap_2a
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_configmap_2a is not changed
+
+ - name: Update configmap
+ k8s:
+ definition:
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: "apply-configmap"
+ namespace: "{{ test_namespace }}"
+ data:
+ one: "1"
+ three: "3"
+ four: "4"
+ apply: yes
+ register: k8s_configmap_3
+
+ - name: Ensure that configmap has been correctly updated
+ assert:
+ that:
+ - k8s_configmap_3 is changed
+ - "'four' in k8s_configmap_3.result.data"
+ - "'two' not in k8s_configmap_3.result.data"
+
+ - name: Add a service
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8080
+ targetPort: 8080
+ apply: yes
+ register: k8s_service
+
+ - name: Add exactly same service
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8080
+ targetPort: 8080
+ apply: yes
+ register: k8s_service_2
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_service_2 is not changed
+
+ - name: Add exactly same service in check mode
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8080
+ targetPort: 8080
+ apply: yes
+ register: k8s_service_3
+ check_mode: yes
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_service_3 is not changed
+
+ - name: Change service ports
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8081
+ targetPort: 8081
+ apply: yes
+ register: k8s_service_4
+
+ - name: Check ports are correct
+ assert:
+ that:
+ - k8s_service_4 is changed
+ - k8s_service_4.result.spec.ports | length == 1
+ - k8s_service_4.result.spec.ports[0].port == 8081
+
+ - name: Insert new service port
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: mesh
+ port: 8080
+ targetPort: 8080
+ - name: http
+ port: 8081
+ targetPort: 8081
+ apply: yes
+ register: k8s_service_4
+
+ - name: Check ports are correct
+ assert:
+ that:
+ - k8s_service_4 is changed
+ - k8s_service_4.result.spec.ports | length == 2
+ - k8s_service_4.result.spec.ports[0].port == 8080
+ - k8s_service_4.result.spec.ports[1].port == 8081
+
+ - name: Remove new service port (check mode)
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8081
+ targetPort: 8081
+ apply: yes
+ check_mode: yes
+ register: k8s_service_check
+
+ - name: Check ports are correct
+ assert:
+ that:
+ - k8s_service_check is changed
+ - k8s_service_check.result.spec.ports | length == 1
+ - k8s_service_check.result.spec.ports[0].port == 8081
+
+ - name: Remove new service port
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apply-svc
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ app: whatever
+ ports:
+ - name: http
+ port: 8081
+ targetPort: 8081
+ apply: yes
+ register: k8s_service_5
+
+ - name: Check ports are correct
+ assert:
+ that:
+ - k8s_service_5 is changed
+ - k8s_service_5.result.spec.ports | length == 1
+ - k8s_service_5.result.spec.ports[0].port == 8081
+
+ - name: Add a serviceaccount
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+
+ - name: Add a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ vars:
+ k8s_pod_name: apply-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-green
+ k8s_pod_service_account: apply-deploy
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ k8s_pod_resources:
+ requests:
+ cpu: 100m
+ memory: 100Mi
+ limits:
+ cpu: 100m
+ memory: 100Mi
+
+ - name: Update the earlier deployment in check mode
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ check_mode: yes
+ vars:
+ k8s_pod_name: apply-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-purple
+ k8s_pod_service_account: apply-deploy
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ k8s_pod_resources:
+ requests:
+ cpu: 50m
+ limits:
+ cpu: 50m
+ memory: 50Mi
+ register: update_deploy_check_mode
+
+ - name: Ensure check mode change took
+ assert:
+ that:
+ - update_deploy_check_mode is changed
+ - "update_deploy_check_mode.result.spec.template.spec.containers[0].image == 'gcr.io/kuar-demo/kuard-amd64:v0.10.0-purple'"
+
+ - name: Update the earlier deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ vars:
+ k8s_pod_name: apply-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-purple
+ k8s_pod_service_account: apply-deploy
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ k8s_pod_resources:
+ requests:
+ cpu: 50m
+ limits:
+ cpu: 50m
+ memory: 50Mi
+ register: update_deploy_for_real
+
+ - name: Ensure change took
+ assert:
+ that:
+ - update_deploy_for_real is changed
+ - "update_deploy_for_real.result.spec.template.spec.containers[0].image == 'gcr.io/kuar-demo/kuard-amd64:v0.10.0-purple'"
+
+ - name: Remove the serviceaccount
+ k8s:
+ state: absent
+ definition:
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+
+ - name: Apply deployment after service account removed
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: apply-deploy
+ namespace: "{{ test_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ vars:
+ k8s_pod_name: apply-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-green
+ k8s_pod_service_account: apply-deploy
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ k8s_pod_resources:
+ requests:
+ cpu: 50m
+ limits:
+ cpu: 50m
+ memory: 50Mi
+ register: deploy_after_serviceaccount_removal
+ ignore_errors: yes
+
+ - name: Ensure that updating deployment after service account removal failed
+ assert:
+ that:
+ - deploy_after_serviceaccount_removal is failed
+
+ - name: Add a secret
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apply-secret
+ namespace: "{{ test_namespace }}"
+ type: Opaque
+ stringData:
+ foo: bar
+ register: k8s_secret
+
+ - name: Check secret was created
+ assert:
+ that:
+ - k8s_secret is changed
+ - k8s_secret.result.data.foo
+
+ - name: Add same secret
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apply-secret
+ namespace: "{{ test_namespace }}"
+ type: Opaque
+ stringData:
+ foo: bar
+ register: k8s_secret
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_secret is not changed
+
+ - name: Add same secret with check mode on
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apply-secret
+ namespace: "{{ test_namespace }}"
+ type: Opaque
+ stringData:
+ foo: bar
+ check_mode: yes
+ register: k8s_secret
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_secret is not changed
+
+ - name: Add same secret with check mode on using data
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: apply-secret
+ namespace: "{{ test_namespace }}"
+ type: Opaque
+ data:
+ foo: YmFy
+ check_mode: yes
+ register: k8s_secret
+
+ - name: Check nothing changed
+ assert:
+ that:
+ - k8s_secret is not changed
+
+ - name: Create network policy (egress array with empty dict)
+ k8s:
+ namespace: "{{ test_namespace }}"
+ apply: true
+ definition:
+ kind: NetworkPolicy
+ apiVersion: networking.k8s.io/v1
+ metadata:
+ name: apply-netpolicy
+ labels:
+ app: apply-netpolicy
+ annotations:
+ {}
+ spec:
+ podSelector:
+ matchLabels:
+ app: apply-netpolicy
+ policyTypes:
+ - Ingress
+ - Egress
+ ingress:
+ - ports:
+ - port: 9093
+ protocol: TCP
+ egress:
+ - {}
+
+ - name: Apply network policy
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ kind: NetworkPolicy
+ apiVersion: networking.k8s.io/v1
+ metadata:
+ name: apply-netpolicy
+ labels:
+ app: apply-netpolicy
+ annotations:
+ {}
+ spec:
+ podSelector:
+ matchLabels:
+ app: apply-netpolicy
+ policyTypes:
+ - Ingress
+ - Egress
+ ingress:
+ - ports:
+ - port: 9093
+ protocol: TCP
+ egress:
+ - {}
+ apply: true
+ register: k8s_networkpolicy
+
+ - name: Check that nothing changed
+ assert:
+ that:
+ - k8s_networkpolicy is not changed
+
+ # Server Side Apply
+ - name: Create Configmap using server side apply - field_manager not specified
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-0
+ apply: yes
+ server_side_apply:
+ force_conflicts: false
+ register: result
+ ignore_errors: true
+
+ - name: Check that configmap creation failed
+ assert:
+ that:
+ - result is failed
+ - '"field_manager" in result.msg'
+
+ - name: Create Configmap using server side apply
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-0
+ apply: yes
+ server_side_apply:
+ field_manager: "manager-00"
+ register: result
+
+ - name: Check configmap was created with expected manager
+ assert:
+ that:
+ - result is changed
+ - result.result.metadata.managedFields | length == 1
+ - result.result.metadata.managedFields[0].manager == 'manager-00'
+
+ - name: Apply ConfigMap using same parameters
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-0
+ apply: yes
+ server_side_apply:
+ field_manager: "manager-00"
+ register: result
+
+ - name: Assert that nothing change using check_mode
+ assert:
+ that:
+ - result is not changed
+
+ - name: Apply ConfigMap adding new manager
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-0
+ apply: yes
+ server_side_apply:
+ field_manager: "manager-01"
+ register: result
+
+ - name: Assert that number of manager has increased
+ assert:
+ that:
+ - result is changed
+ - result.result.metadata.managedFields | length == 2
+
+ - name: Apply changes to Configmap using new field_manager
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-1
+ apply: yes
+ server_side_apply:
+ field_manager: "manager-02"
+ register: result
+ ignore_errors: true
+
+ - name: assert that operation failed with conflicts
+ assert:
+ that:
+ - result is failed
+ - result.reason == 'Conflict'
+
+ - name: Apply changes to Configmap using new field_manager and force_conflicts
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: server-side-cm
+ data:
+ key: value-1
+ apply: yes
+ server_side_apply:
+ field_manager: "manager-02"
+ force_conflicts: true
+ register: result
+
+ - name: assert that operation failed with conflicts
+ assert:
+ that:
+ - result is changed
+ - result.result.metadata.managedFields | length == 1
+ - result.result.metadata.managedFields[0].manager == 'manager-02'
+ - result.result.data.key == 'value-1'
+
+ # check_mode with server side apply
+ - name: Ensure namespace does not exist
+ k8s:
+ state: absent
+ kind: Namespace
+ name: testing
+ wait: true
+
+ - name: Create namespace using server_side_apply=true and check_mode=true
+ k8s:
+ apply: true
+ server_side_apply:
+ field_manager: ansible
+ definition:
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing
+ check_mode: true
+ register: _create
+
+ - name: Ensure namespace was not created
+ k8s_info:
+ kind: Namespace
+ name: testing
+ register: _info
+
+ - name: Validate that check_mode reported change even if namespace was not created
+ assert:
+ that:
+ - _create is changed
+ - not _info.resources
+
+ # server side apply over kubernetes client releases
+ - name: Create temporary directory
+ tempfile:
+ state: directory
+ suffix: .server
+ register: path
+
+ - set_fact:
+ virtualenv_src: "{{ path.path }}"
+
+ - include_tasks: tasks/server_side_apply.yml
+ with_items:
+ - '24.2.0'
+ - '25.2.0a1'
+ - '25.3.0b1'
+ - '25.3.0'
+ - '23.6.0'
+
+ always:
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+
+ - name: Delete temporary directory
+ file:
+ path: "{{ virtualenv_src }}"
+ state: absent
+ ignore_errors: true
+ when: virtualenv_src is defined
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml
new file mode 100644
index 00000000..d50bf147
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml
@@ -0,0 +1,24 @@
+- set_fact:
+ kubernetes_version: "kubernetes=={{ item }}"
+ virtualenv_path: "{{ virtualenv_src }}/venv{{ item | replace('.', '') }}"
+
+- name: Install kubernetes version
+ pip:
+ name:
+ - '{{ kubernetes_version }}'
+ virtualenv_command: "virtualenv --python {{ ansible_python_interpreter }}"
+ virtualenv: "{{ virtualenv_path }}"
+
+- name: Update namespace using server side apply
+ k8s:
+ apply: true
+ server_side_apply:
+ field_manager: "ansible{{ item | replace('.', '') }}"
+ force_conflicts: true
+ definition:
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing
+ vars:
+ ansible_python_interpreter: "{{ virtualenv_path }}/bin/python"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/aliases
new file mode 100644
index 00000000..07810824
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/aliases
@@ -0,0 +1,3 @@
+time=30
+k8s
+k8s_info \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/defaults/main.yml
new file mode 100644
index 00000000..2fd8c70c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+test_namespace: "check-mode-ns"
+
+test_config:
+ - k8s_release: "kubernetes < 18.20.0"
+ dry_run: false
+ virtualenv: 'env1'
+ - k8s_release: "kubernetes >= 18.20.0"
+ dry_run: true
+ virtualenv: 'env2'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/meta/main.yml
new file mode 100644
index 00000000..2e3ba2fa
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/check_mode.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/check_mode.yml
new file mode 100644
index 00000000..5d9e2caa
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/check_mode.yml
@@ -0,0 +1,32 @@
+- name: Create virtualenv with kubernetes release
+ pip:
+ name:
+ - '{{ item.k8s_release }}'
+ virtualenv_command: "virtualenv --python {{ ansible_python_interpreter }}"
+ virtualenv: "{{ tmpdir }}/{{ item.virtualenv }}"
+
+- name: Create resource using check mode
+ k8s:
+ kind: Namespace
+ name: '{{ test_namespace }}'
+ check_mode: true
+ register: _create
+
+- name: Ensure namespace was not created
+ k8s_info:
+ kind: Namespace
+ name: '{{ test_namespace }}'
+ register: info
+ failed_when: info.resources | length > 0
+
+- name: Ensure server side dry_run has being used
+ assert:
+ that:
+ - '"creationTimestamp" in _create.result.metadata'
+ when: item.dry_run
+
+- name: Ensure server side dry_run was not used
+ assert:
+ that:
+ - '"creationTimestamp" in _create.result.metadata'
+ when: not item.dry_run
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/main.yml
new file mode 100644
index 00000000..60af294e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_check_mode/tasks/main.yml
@@ -0,0 +1,19 @@
+- name: create temporary directory for tests
+ tempfile:
+ suffix: .k8s
+ state: directory
+ register: _path
+
+- block:
+ - include_tasks: tasks/check_mode.yml
+ with_items: '{{ test_config }}'
+
+ vars:
+ tmpdir: '{{ _path.path }}'
+
+ always:
+ - name: Delete temporaray directory
+ file:
+ state: absent
+ path: '{{ tmpdir }}'
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/aliases
new file mode 100644
index 00000000..00696503
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/aliases
@@ -0,0 +1,2 @@
+k8s_cluster_info
+time=9
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/tasks/main.yml
new file mode 100644
index 00000000..939cedf6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_cluster_info/tasks/main.yml
@@ -0,0 +1,24 @@
+---
+- name: Get Information about All APIs
+ k8s_cluster_info:
+ register: api_details
+
+- name: Print all APIs for debugging
+ debug:
+ msg: "{{ api_details.apis }}"
+
+- name: Get core API version
+ set_fact:
+ crd: "{{ api_details.apis['apiextensions.k8s.io/v1'] }}"
+ host: "{{ api_details.connection['host'] }}"
+ client_version: "{{ api_details.version['client'] }}"
+
+- name: Check if all APIs are present
+ assert:
+ that:
+ - api_details.apis is defined
+ - api_details.apis.v1.Secret is defined
+ - api_details.apis.v1.Service is defined
+ - crd is defined
+ - host is defined
+ - client_version is defined
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/aliases
new file mode 100644
index 00000000..1e430360
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/aliases
@@ -0,0 +1,4 @@
+k8s_exec
+k8s_cp
+k8s
+time=101
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/defaults/main.yml
new file mode 100644
index 00000000..aaf46330
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/defaults/main.yml
@@ -0,0 +1,16 @@
+---
+# defaults file for k8copy
+test_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
+
+pod_without_executable_find:
+ name: openjdk-pod
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/archive.tar b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/archive.tar
new file mode 100644
index 00000000..be47f0b2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/archive.tar
Binary files differ
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/collection.txt b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/collection.txt
new file mode 100644
index 00000000..2ac78be4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/collection.txt
@@ -0,0 +1 @@
+kubernetes.core
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/module.txt b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/module.txt
new file mode 100644
index 00000000..c9931cf8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/ansible/module.txt
@@ -0,0 +1 @@
+k8s_cp
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/file.txt b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/file.txt
new file mode 100644
index 00000000..38c57aac
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/file.txt
@@ -0,0 +1 @@
+This is a simple file used to test k8s_cp module on ansible.
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/teams/ansible.txt b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/teams/ansible.txt
new file mode 100644
index 00000000..0073319b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/data/teams/ansible.txt
@@ -0,0 +1,2 @@
+cloud team
+content team
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_file.txt b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_file.txt
new file mode 100644
index 00000000..b26bf561
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_file.txt
@@ -0,0 +1 @@
+This content will be copied into remote Pod. \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_zip_file.txt.gz b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_zip_file.txt.gz
new file mode 100644
index 00000000..7ecf120e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/files/simple_zip_file.txt.gz
Binary files differ
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/k8s_create_file.py b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/k8s_create_file.py
new file mode 100644
index 00000000..6898c36a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/k8s_create_file.py
@@ -0,0 +1,93 @@
+#!/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_create_file
+
+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()
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/kubectl_file_compare.py b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/kubectl_file_compare.py
new file mode 100644
index 00000000..bcf09783
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/library/kubectl_file_compare.py
@@ -0,0 +1,247 @@
+#!/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()
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/meta/main.yml
new file mode 100644
index 00000000..4b952614
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/meta/main.yml
@@ -0,0 +1,5 @@
+---
+collections:
+ - kubernetes.core
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/main.yml
new file mode 100644
index 00000000..519be8f6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/main.yml
@@ -0,0 +1,36 @@
+---
+- set_fact:
+ copy_namespace: "{{ test_namespace }}"
+
+- block:
+ # 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_check_mode.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
+ - include_tasks: test_copy_item_with_space_in_its_name.yml
+
+ always:
+
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ copy_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_check_mode.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_check_mode.yml
new file mode 100644
index 00000000..b246047e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_check_mode.yml
@@ -0,0 +1,142 @@
+---
+- name: Create temporary directory for testing
+ tempfile:
+ state: directory
+ suffix: ansible-k8s-copy
+ register: tmpdir
+
+- block:
+ # setup
+ - name: Create local files for testing
+ copy:
+ content: "{{ item.content }}"
+ dest: "{{ local_dir_path }}/{{ item.dest }}"
+ with_items: "{{ test_files }}"
+
+ - name: Create directory into Pod
+ k8s_exec:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ command: "mkdir {{ pod_dir_path }}"
+
+ - name: Create files into Pod
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ remote_path: "{{ pod_dir_path }}/{{ item.dest }}"
+ content: "{{ item.content }}"
+ state: to_pod
+ with_items: "{{ test_files }}"
+
+ # Test copy into Pod using check_mode=true
+ - name: Copy text file into remote pod
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ remote_path: "{{ pod_dir_path }}/ansible.txt"
+ local_path: "{{ local_dir_path }}/{{ test_files[0].dest }}"
+ state: to_pod
+ check_mode: true
+ register: copy_file
+
+ - name: Ensure task is changed
+ assert:
+ that:
+ - copy_file is changed
+
+ - name: Ensure file does not exists into Pod
+ k8s_exec:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ command: "test -e {{ pod_dir_path }}/ansible.txt"
+ register: test_file
+ failed_when: test_file.return_code == 0
+
+ - name: Copy directory into Pod
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ remote_path: "{{ pod_dir_path }}/mydir"
+ local_path: "{{ local_dir_path }}"
+ state: to_pod
+ check_mode: true
+ register: copy_dir
+
+ - name: Ensure task is changed
+ assert:
+ that:
+ - copy_dir is changed
+
+ - name: Ensure file does not exists into Pod
+ k8s_exec:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ command: "test -e {{ pod_dir_path }}/mydir"
+ register: test_dir
+ failed_when: test_dir.return_code == 0
+
+ # Test copy from pod using check_mode=true
+ - name: Copy file from Pod into local file system
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ remote_path: "{{ pod_dir_path }}/{{ test_files[0].dest }}"
+ local_path: "{{ local_dir_path }}/ansible.txt"
+ state: from_pod
+ check_mode: true
+ register: copy_file
+
+ - name: Ensure task is changed
+ assert:
+ that:
+ - copy_file is changed
+
+ - name: Ensure file does not exists into local file system
+ stat:
+ path: "{{ local_dir_path }}/ansible.txt"
+ register: testfile
+ failed_when: testfile.stat.exists
+
+ - name: Copy directory from Pod into local file system
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ remote_path: "{{ pod_dir_path }}"
+ local_path: "{{ local_dir_path }}/mydir"
+ state: from_pod
+ check_mode: true
+ register: _dir
+
+ - name: Ensure task is changed
+ assert:
+ that:
+ - _dir is changed
+
+ - name: Ensure directory does not exist into local file system
+ stat:
+ path: "{{ local_dir_path }}/mydir"
+ register: testdir
+ failed_when: testdir.stat.exists
+
+ vars:
+ local_dir_path: "{{ tmpdir.path }}"
+ pod_dir_path: "/tmp/test_check_mode"
+ test_files:
+ - content: "collection = kubernetes.core"
+ dest: collection.txt
+ - content: "modules = k8s_cp and k8s_exec"
+ dest: modules.txt
+
+ always:
+ - name: Delete temporary directory
+ file:
+ state: absent
+ path: "{{ local_dir_path }}"
+ ignore_errors: true
+
+ - name: Delete temporary directory created into Pod
+ k8s_exec:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_with_one_container.name }}'
+ command: 'rm -r {{ pod_dir_path }}'
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_directory.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_directory.yml
new file mode 100644
index 00000000..ed2ad85d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_directory.yml
@@ -0,0 +1,135 @@
+---
+- 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'
+
+ - 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'
+
+ - 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
+
+ - 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
+
+ # Test copy from Pod where the executable 'find' is missing
+ - name: Ensure 'find' is missing from Pod
+ k8s_exec:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ command: 'find'
+ ignore_errors: true
+ register: _result
+
+ - name: Validate that 'find' executable is missing from Pod
+ assert:
+ that:
+ - _result is failed
+ fail_msg: "Pod contains 'find' executable, therefore we cannot run the next tasks."
+
+ - name: Copy files into container
+ k8s_cp:
+ namespace: "{{ copy_namespace }}"
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: '{{ item.path }}'
+ content: '{{ item.content }}'
+ state: to_pod
+ with_items:
+ - path: /ansible/root.txt
+ content: this file is located at the root directory
+ - path: /ansible/.hidden_root.txt
+ content: this hidden file is located at the root directory
+ - path: /ansible/.sudir/root.txt
+ content: this file is located at the root of the sub directory
+ - path: /ansible/.sudir/.hidden_root.txt
+ content: this hidden file is located at the root of the sub directory
+
+ - name: Delete existing directory
+ file:
+ path: /tmp/openjdk-files
+ state: absent
+ ignore_errors: true
+
+ - name: copy directory from Pod into local filesystem (new directory to create)
+ k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: /ansible
+ local_path: /tmp/openjdk-files
+ state: from_pod
+
+ - name: Compare directories
+ kubectl_file_compare:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: /ansible
+ local_path: /tmp/openjdk-files
+
+ 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
+ - /tmp/openjdk-files
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml
new file mode 100644
index 00000000..b1e799bf
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml
@@ -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"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_file.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_file.yml
new file mode 100644
index 00000000..51d177c0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_file.yml
@@ -0,0 +1,199 @@
+---
+- 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')}}"
+
+ - 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
+
+ # 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'
+
+ - 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
+
+ # 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'
+
+ - 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
+
+ # 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 }}'
+
+ 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
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_item_with_space_in_its_name.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_item_with_space_in_its_name.yml
new file mode 100644
index 00000000..df3efd6b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_item_with_space_in_its_name.yml
@@ -0,0 +1,120 @@
+---
+- name: create temporary directory for testing
+ tempfile:
+ state: directory
+ suffix: .space
+ register: _tmpdir
+
+- block:
+ - set_fact:
+ some_file_content: 'this content will be stored into a file in the remote pod which has space in its name'
+
+ - name: create file with space in the name on remote pod
+ kubernetes.core.k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/file name space .txt"
+ content: '{{ some_file_content }}'
+ state: to_pod
+
+ - set_fact:
+ local_file_path: '{{ _tmpdir.path }}/file_from_pod.txt'
+
+ - name: copy file (with space in its name) from pod to local filesystem
+ kubernetes.core.k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/file name space .txt"
+ local_path: '{{ local_file_path }}'
+ state: from_pod
+
+ - name: Ensure file was successfully copied
+ assert:
+ that:
+ - lookup('file', local_file_path) == some_file_content
+
+ - set_fact:
+ dir_config:
+ - 'test\ dir\ 01/file1.txt'
+ - 'test\ dir\ 01/file with space in its name.txt'
+ - 'test\ dir\ 02/file2.txt'
+ - 'test\ dir\ 02/another file with space in its name '
+ - 'test\ dir\ 03/file3.txt'
+ - 'test\ dir\ 03/a third file with space in its name'
+
+ - set_fact:
+ escape_char: \
+
+ - name: create directories on Pod
+ kubernetes.core.k8s_exec:
+ namespace: "{{ copy_namespace }}"
+ pod: "{{ pod_without_executable_find.name }}"
+ command: "mkdir -p /ansible_testing/{{ item | dirname }}"
+ with_items: '{{ dir_config }}'
+
+ - name: create files on remote pod
+ kubernetes.core.k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/ansible_testing/{{ item | replace(escape_char, '') }}"
+ content: "This content is from file named: {{ item | replace(escape_char, '') }}"
+ state: to_pod
+ with_items: '{{ dir_config }}'
+
+ - set_fact:
+ local_copy: '{{ _tmpdir.path }}/local_partial_copy'
+ full_copy: '{{ _tmpdir.path }}/local_full_copy'
+
+ - name: create local directories
+ file:
+ state: directory
+ path: '{{ item }}'
+ with_items:
+ - '{{ local_copy }}'
+ - '{{ full_copy }}'
+
+ - name: Copy directory from Pod into local filesystem
+ kubernetes.core.k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/ansible_testing/{{ item }}"
+ local_path: '{{ local_copy }}'
+ state: from_pod
+ with_items:
+ - 'test dir 01'
+ - 'test dir 02'
+ - 'test dir 03'
+
+ - name: Compare resulting directory
+ kubectl_file_compare:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/ansible_testing/{{ item }}"
+ local_path: '{{ local_copy }}/{{ item }}'
+ with_items:
+ - 'test dir 01'
+ - 'test dir 02'
+ - 'test dir 03'
+
+ - name: Copy remote directory into local file system
+ kubernetes.core.k8s_cp:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/ansible_testing"
+ local_path: '{{ full_copy }}'
+ state: from_pod
+
+ - name: Compare resulting directory
+ kubectl_file_compare:
+ namespace: '{{ copy_namespace }}'
+ pod: '{{ pod_without_executable_find.name }}'
+ remote_path: "/ansible_testing"
+ local_path: '{{ full_copy }}/ansible_testing'
+
+ always:
+ - name: Delete temporary directory
+ file:
+ state: absent
+ path: '{{ _tmpdir.path }}'
+ ignore_errors: true
+ when: _tmpdir is defined
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_large_file.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_large_file.yml
new file mode 100644
index 00000000..6758202e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_copy_large_file.yml
@@ -0,0 +1,101 @@
+---
+- 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"
+
+ - 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"
+
+ # 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
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_multi_container_pod.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_multi_container_pod.yml
new file mode 100644
index 00000000..d278855b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/tasks/test_multi_container_pod.yml
@@ -0,0 +1,67 @@
+---
+- 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 }}'
+
+- 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 }}'
+ 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'
+ 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'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/templates/pods_definition.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/templates/pods_definition.j2
new file mode 100644
index 00000000..ee6c6f65
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_copy/templates/pods_definition.j2
@@ -0,0 +1,45 @@
+---
+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
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: '{{ pod_without_executable_find.name }}'
+spec:
+ containers:
+ - name: openjdk17
+ image: openjdk:17
+ command:
+ - /bin/sh
+ - -c
+ - while true;do date;sleep 5; done \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/aliases
new file mode 100644
index 00000000..cd32372b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/aliases
@@ -0,0 +1,2 @@
+time=22
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/defaults/main.yml
new file mode 100644
index 00000000..9ccaec0b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "crd"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/crd-resource.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/crd-resource.yml
new file mode 100644
index 00000000..23d0663c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/crd-resource.yml
@@ -0,0 +1,21 @@
+---
+apiVersion: certmanager.k8s.io/v1alpha1
+kind: Certificate
+metadata:
+ name: acme-crt
+spec:
+ secretName: acme-crt-secret
+ dnsNames:
+ - foo.example.com
+ - bar.example.com
+ acme:
+ config:
+ - ingressClass: nginx
+ domains:
+ - foo.example.com
+ - bar.example.com
+ issuerRef:
+ name: letsencrypt-prod
+ # We can reference ClusterIssuers by changing the kind here.
+ # The default value is Issuer (i.e. a locally namespaced Issuer)
+ kind: Issuer
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/setup-crd.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/setup-crd.yml
new file mode 100644
index 00000000..15debdbd
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/files/setup-crd.yml
@@ -0,0 +1,53 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: certificates.certmanager.k8s.io
+ annotations:
+ "api-approved.kubernetes.io": "https://github.com/kubernetes/kubernetes/pull/78458"
+spec:
+ group: certmanager.k8s.io
+ versions:
+ - name: v1alpha1
+ served: true
+ storage: true
+ schema:
+ openAPIV3Schema:
+ type: object
+ properties:
+ certificate:
+ type: string
+ secretName:
+ type: string
+ dnsNames:
+ type: array
+ items:
+ type: string
+ acme:
+ type: object
+ properties:
+ config:
+ type: array
+ items:
+ type: object
+ properties:
+ ingressClass:
+ type: string
+ domains:
+ type: array
+ items:
+ type: string
+ issuerRef:
+ type: object
+ properties:
+ name:
+ type: string
+ kind:
+ type: string
+ scope: Namespaced
+ names:
+ kind: Certificate
+ plural: certificates
+ shortNames:
+ - cert
+ - certs
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/tasks/main.yml
new file mode 100644
index 00000000..f26e3419
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_crd/tasks/main.yml
@@ -0,0 +1,61 @@
+---
+- block:
+ - name: Install custom resource definitions
+ k8s:
+ definition: "{{ lookup('file', 'setup-crd.yml') }}"
+
+ - name: Pause 5 seconds to avoid race condition
+ pause:
+ seconds: 5
+
+ - name: Create custom resource definition
+ k8s:
+ definition: "{{ lookup('file', 'crd-resource.yml') }}"
+ namespace: "{{ test_namespace }}"
+ apply: "{{ create_crd_with_apply | default(omit) }}"
+ register: create_crd
+
+ - name: Patch custom resource definition
+ k8s:
+ definition: "{{ lookup('file', 'crd-resource.yml') }}"
+ namespace: "{{ test_namespace }}"
+ register: recreate_crd
+ ignore_errors: yes
+
+ - name: Assert that recreating crd is as expected
+ assert:
+ that:
+ - recreate_crd is not failed
+
+ - block:
+ - name: Recreate custom resource definition with merge_type
+ k8s:
+ definition: "{{ lookup('file', 'crd-resource.yml') }}"
+ merge_type:
+ - merge
+ namespace: "{{ test_namespace }}"
+ register: recreate_crd_with_merge
+
+ - name: Recreate custom resource definition with merge_type list
+ k8s:
+ definition: "{{ lookup('file', 'crd-resource.yml') }}"
+ merge_type:
+ - strategic-merge
+ - merge
+ namespace: "{{ test_namespace }}"
+ register: recreate_crd_with_merge_list
+ when: recreate_crd is successful
+
+
+ - name: Remove crd
+ k8s:
+ definition: "{{ lookup('file', 'crd-resource.yml') }}"
+ namespace: "{{ test_namespace }}"
+ state: absent
+
+ always:
+ - name: Remove crd namespace
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/aliases
new file mode 100644
index 00000000..66de780e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/aliases
@@ -0,0 +1,3 @@
+time=70
+k8s_info
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/defaults/main.yml
new file mode 100644
index 00000000..43db9196
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/defaults/main.yml
@@ -0,0 +1,25 @@
+---
+k8s_pod_template:
+ metadata:
+ labels:
+ app: "{{ k8s_pod_name }}"
+ spec:
+ serviceAccount: "default"
+ containers:
+ - image: "{{ k8s_pod_image }}"
+ imagePullPolicy: Always
+ name: "{{ k8s_pod_name }}"
+ command: []
+ readinessProbe:
+ initialDelaySeconds: 15
+ exec:
+ command:
+ - /bin/true
+ resources:
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
+ ports: []
+ env: []
+
+test_namespace: "delete"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/tasks/main.yml
new file mode 100644
index 00000000..88edf59b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_delete/tasks/main.yml
@@ -0,0 +1,129 @@
+---
+- block:
+ - name: Add a daemonset
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: delete-daemonset
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: 400
+ vars:
+ k8s_pod_name: delete-ds
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1
+ register: ds
+
+ - name: Check that daemonset wait worked
+ assert:
+ that:
+ - ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled
+
+ - name: Check if pods exist
+ k8s_info:
+ namespace: "{{ test_namespace }}"
+ kind: Pod
+ label_selectors:
+ - "app={{ k8s_pod_name }}"
+ vars:
+ k8s_pod_name: delete-ds
+ register: pods_create
+
+ - name: Assert that there are pods
+ assert:
+ that:
+ - pods_create.resources
+
+ - name: Remove the daemonset
+ k8s:
+ kind: DaemonSet
+ name: delete-daemonset
+ namespace: "{{ test_namespace }}"
+ state: absent
+ wait: yes
+
+ - name: Show status of pods
+ k8s_info:
+ namespace: "{{ test_namespace }}"
+ kind: Pod
+ label_selectors:
+ - "app={{ k8s_pod_name }}"
+ vars:
+ k8s_pod_name: delete-ds
+
+ - name: Wait for background deletion
+ pause:
+ seconds: 30
+
+ - name: Check if pods still exist
+ k8s_info:
+ namespace: "{{ test_namespace }}"
+ kind: Pod
+ label_selectors:
+ - "app={{ k8s_pod_name }}"
+ vars:
+ k8s_pod_name: delete-ds
+ register: pods_delete
+
+ - name: Assert that deleting the daemonset deleted the pods
+ assert:
+ that:
+ - not pods_delete.resources
+
+ # test deletion using label selector
+ - name: Deploy load balancer
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ test: deletion
+ name: "deletion-svc-{{ item }}"
+ spec:
+ ports:
+ - port: 5000
+ targetPort: 5000
+ selector:
+ test: deletion
+ type: LoadBalancer
+ with_items:
+ - "01"
+ - "02"
+ - "03"
+
+ - name: Delete services using label selector
+ kubernetes.core.k8s:
+ api_version: v1
+ namespace: "{{ test_namespace }}"
+ kind: Service
+ state: absent
+ label_selectors:
+ - test=deletion
+
+ - name: list services using label selector
+ k8s_info:
+ kind: Service
+ namespace: "{{ test_namespace }}"
+ label_selectors:
+ - test=deletion
+ register: _result
+
+ - name: Validate that all services were deleted
+ assert:
+ that:
+ - _result.resources | length == 0
+
+ always:
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/aliases
new file mode 100644
index 00000000..05895d24
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/aliases
@@ -0,0 +1,2 @@
+time=20
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/defaults/main.yml
new file mode 100644
index 00000000..5e2db246
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "diff"
+diff_configmap: "diff-configmap"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/tasks/main.yml
new file mode 100644
index 00000000..81e3dacc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/tasks/main.yml
@@ -0,0 +1,148 @@
+---
+- block:
+ - set_fact:
+ diff_namespace: "{{ test_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.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.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.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.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.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 }}'
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/templates/pod.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/templates/pod.j2
new file mode 100644
index 00000000..5bdb6e0a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_diff/templates/pod.j2
@@ -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 \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/aliases
new file mode 100644
index 00000000..07e77915
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/aliases
@@ -0,0 +1,4 @@
+k8s_drain
+k8s
+k8s_info
+time=4m
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/defaults/main.yml
new file mode 100644
index 00000000..918c67d2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "drain"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/tasks/main.yml
new file mode 100644
index 00000000..f16f8aff
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_drain/tasks/main.yml
@@ -0,0 +1,367 @@
+---
+- block:
+ - name: Set common facts
+ set_fact:
+ drain_daemonset_name: "promotheus-dset"
+ drain_pod_name: "pod-drain"
+ drain_deployment_emptydir_name: "deployment-emptydir-drain"
+
+ # 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: "{{ test_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: '{{ test_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: '{{ test_namespace }}'
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ 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: Create Deployment with an emptyDir volume.
+ k8s:
+ namespace: '{{ test_namespace }}'
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: '{{ drain_deployment_emptydir_name }}'
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ drain: emptyDir
+ template:
+ metadata:
+ labels:
+ drain: emptyDir
+ spec:
+ metadata:
+ labels:
+ drain: emptyDir
+ 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
+ volumeMounts:
+ - mountPath: /emptydir
+ name: emptydir
+ volumes:
+ - name: emptydir
+ emptyDir: {}
+
+ - name: Register emptyDir Pod name
+ k8s_info:
+ namespace: '{{ test_namespace }}'
+ kind: Pod
+ label_selectors:
+ - "drain = emptyDir"
+ register: emptydir_pod_result
+ failed_when:
+ - emptydir_pod_result.resources | length != 1
+
+ - 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: '{{ test_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'
+ - '"cannot delete Pods with local storage" in drain_result.msg'
+
+ - name: Drain node using ignore_daemonsets, force, and delete_emptydir_data options
+ k8s_drain:
+ state: drain
+ name: '{{ node_to_drain }}'
+ delete_options:
+ force: true
+ ignore_daemonsets: true
+ delete_emptydir_data: 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: '{{ test_namespace }}'
+ kind: Pod
+ name: '{{ drain_pod_name }}'
+ register: _result
+ failed_when: _result.resources
+
+ - name: assert that emptyDir pod was deleted
+ k8s_info:
+ namespace: '{{ test_namespace }}'
+ kind: Pod
+ name: "{{ emptydir_pod_result.resources[0].metadata.name }}"
+ register: _result
+ failed_when: _result.resources | length != 0
+
+ - name: Test drain idempotency
+ k8s_drain:
+ state: drain
+ name: '{{ node_to_drain }}'
+ delete_options:
+ force: true
+ ignore_daemonsets: true
+ delete_emptydir_data: true
+ register: drain_result
+
+ - name: Check idempotency
+ assert:
+ that:
+ - drain_result is not changed
+
+ - name: Get DaemonSet
+ k8s_info:
+ kind: DaemonSet
+ namespace: '{{ test_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 }}'
+
+ - name: Create another Pod
+ k8s:
+ namespace: '{{ test_namespace }}'
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: '{{ drain_pod_name }}-01'
+ 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
+ volumeMounts:
+ - mountPath: /emptydir
+ name: emptydir
+ volumes:
+ - name: emptydir
+ emptyDir: {}
+
+ - name: Drain node using disable_eviction set to yes
+ k8s_drain:
+ state: drain
+ name: '{{ node_to_drain }}'
+ delete_options:
+ force: true
+ disable_eviction: yes
+ terminate_grace_period: 0
+ ignore_daemonsets: yes
+ wait_timeout: 0
+ delete_emptydir_data: true
+ register: disable_evict
+
+ - name: assert that node has been drained
+ assert:
+ that:
+ - disable_evict is changed
+ - '"node {{ node_to_drain }} marked unschedulable." in disable_evict.result'
+
+ - name: assert that unmanaged pod were deleted
+ k8s_info:
+ namespace: '{{ test_namespace }}'
+ kind: Pod
+ name: '{{ drain_pod_name }}-01'
+ register: _result
+ failed_when: _result.resources
+
+ - 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: '{{ test_namespace }}'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/aliases
new file mode 100644
index 00000000..334dc479
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/aliases
@@ -0,0 +1,3 @@
+k8s_exec
+k8s
+time=23
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/defaults/main.yml
new file mode 100644
index 00000000..d87b3e9f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "k8s-exec"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/tasks/main.yml
new file mode 100644
index 00000000..53b0a58d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_exec/tasks/main.yml
@@ -0,0 +1,94 @@
+---
+- vars:
+ k8s_wait_timeout: 400
+ pod: sleep-pod
+ exec_pod_definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ pod }}"
+ namespace: "{{ test_namespace }}"
+ spec:
+ containers:
+ - name: sleeper
+ image: busybox
+ command: ["sleep", "infinity"]
+ multi_container_pod_name: pod-2
+ multi_container_pod_definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ multi_container_pod_name }}"
+ namespace: "{{ test_namespace }}"
+ spec:
+ containers:
+ - name: sleeper-1
+ image: busybox
+ command: ["sleep", "infinity"]
+ - name: sleeper-2
+ image: busybox
+ command: ["sleep", "infinity"]
+
+ block:
+ - name: "Create a pod"
+ k8s:
+ definition: "{{ exec_pod_definition }}"
+ wait: yes
+ wait_sleep: 1
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - name: "Execute a command"
+ k8s_exec:
+ pod: "{{ pod }}"
+ namespace: "{{ test_namespace }}"
+ command: cat /etc/resolv.conf
+ register: output
+
+ - name: "Show k8s_exec output"
+ debug:
+ var: output
+
+ - name: "Assert k8s_exec output is correct"
+ assert:
+ that:
+ - "'nameserver' in output.stdout"
+
+ - name: Check if rc is returned for the given command
+ k8s_exec:
+ namespace: "{{ test_namespace }}"
+ pod: "{{ pod }}"
+ command: 'false'
+ register: command_status
+ ignore_errors: True
+
+ - name: Check last command status
+ assert:
+ that:
+ - command_status.rc != 0
+ - command_status.return_code != 0
+
+ - name: Create a multi container pod
+ k8s:
+ definition: "{{ multi_container_pod_definition }}"
+ wait: yes
+ wait_sleep: 1
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - name: Execute command on the first container of the pod
+ k8s_exec:
+ pod: "{{ multi_container_pod_name }}"
+ namespace: "{{ test_namespace }}"
+ command: echo hello
+ register: output
+
+ - name: Assert k8s_exec output is correct
+ assert:
+ that:
+ - "'hello' in output.stdout"
+
+ always:
+ - name: "Cleanup namespace"
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/aliases
new file mode 100644
index 00000000..c9ed608b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/aliases
@@ -0,0 +1,3 @@
+time=57
+k8s
+k8s_info \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/defaults/main.yml
new file mode 100644
index 00000000..3d0d3394
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+test_namespace:
+ - testing
+ - testing1
+ - testing2
+ - testing3
+ - testing4
+ - testing5
+ - testing6
+ - test-namespace-module-defaults
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/meta/main.yml
new file mode 100644
index 00000000..2e3ba2fa
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/tasks/main.yml
new file mode 100644
index 00000000..10abccfc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_full/tasks/main.yml
@@ -0,0 +1,509 @@
+---
+- block:
+ - name: Create a namespace
+ k8s:
+ name: testing
+ kind: Namespace
+ register: output
+
+ - name: Show output
+ debug:
+ var: output
+
+ - name: Setting validate_certs to true causes a failure
+ k8s:
+ name: testing
+ kind: Namespace
+ validate_certs: yes
+ ca_cert: /dev/null # invalid CA certificate
+ ignore_errors: yes
+ register: output
+
+ - name: assert that validate_certs caused a failure (and therefore was correctly translated to verify_ssl)
+ assert:
+ that:
+ - output is failed
+
+ - block:
+ - name: Copy default kubeconfig
+ copy:
+ remote_src: yes
+ src: ~/.kube/config
+ dest: ~/.kube/customconfig
+
+ - name: Delete default kubeconfig
+ file:
+ 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: Get currently installed version of kubernetes
+ ansible.builtin.command: python -c "import kubernetes; print(kubernetes.__version__)"
+ register: kubernetes_version
+
+ - name: Using in-memory kubeconfig should succeed
+ kubernetes.core.k8s:
+ name: testing
+ kind: Namespace
+ kubeconfig: "{{ lookup('file', '~/.kube/customconfig') | from_yaml }}"
+ when: kubernetes_version.stdout is version("17.17.0", ">=")
+
+ always:
+ - name: Return kubeconfig
+ copy:
+ remote_src: yes
+ src: ~/.kube/customconfig
+ dest: ~/.kube/config
+ ignore_errors: yes
+
+ - name: Delete custom config
+ file:
+ path: ~/.kube/customconfig
+ state: absent
+ ignore_errors: yes
+
+ - name: Ensure k8s_info works with empty resources
+ k8s_info:
+ kind: Deployment
+ namespace: testing
+ api_version: apps/v1
+ register: k8s_info
+
+ - name: Assert that k8s_info is in correct format
+ assert:
+ that:
+ - "'resources' in k8s_info"
+ - not k8s_info.resources
+
+ - name: Create a service
+ k8s:
+ state: present
+ resource_definition: &svc
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: web
+ namespace: testing
+ labels:
+ app: galaxy
+ service: web
+ spec:
+ selector:
+ app: galaxy
+ service: web
+ ports:
+ - protocol: TCP
+ targetPort: 8000
+ name: port-8000-tcp
+ port: 8000
+ register: output
+
+ - name: Show output
+ debug:
+ var: output
+
+ - name: Create the service again
+ k8s:
+ state: present
+ resource_definition: *svc
+ register: output
+
+ - name: Service creation should be idempotent
+ assert:
+ that: not output.changed
+
+ - name: Create a ConfigMap
+ k8s:
+ kind: ConfigMap
+ name: test-force-update
+ namespace: testing
+ definition:
+ data:
+ key: value
+
+ - name: Force update ConfigMap
+ k8s:
+ kind: ConfigMap
+ name: test-force-update
+ namespace: testing
+ definition:
+ data:
+ key: newvalue
+ force: yes
+
+ - name: Create PVC
+ k8s:
+ state: present
+ inline: &pvc
+ apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ name: elastic-volume
+ namespace: testing
+ spec:
+ resources:
+ requests:
+ storage: 5Gi
+ accessModes:
+ - ReadWriteOnce
+
+ - name: Show output
+ debug:
+ var: output
+
+ - name: Create the PVC again
+ k8s:
+ state: present
+ inline: *pvc
+
+ - name: Ensure PVC creation is idempotent
+ assert:
+ that: not output.changed
+
+ - name: Create deployment
+ k8s:
+ state: present
+ inline: &deployment
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: elastic
+ labels:
+ app: galaxy
+ service: elastic
+ namespace: testing
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: galaxy
+ service: elastic
+ template:
+ metadata:
+ labels:
+ app: galaxy
+ service: elastic
+ spec:
+ containers:
+ - name: elastic
+ volumeMounts:
+ - mountPath: /usr/share/elasticsearch/data
+ name: elastic-volume
+ command: ['elasticsearch']
+ image: 'ansible/galaxy-elasticsearch:2.4.6'
+ volumes:
+ - name: elastic-volume
+ persistentVolumeClaim:
+ claimName: elastic-volume
+ strategy:
+ type: RollingUpdate
+ register: output
+
+ - name: Show output
+ debug:
+ var: output
+
+ - name: Create deployment again
+ k8s:
+ state: present
+ inline: *deployment
+ register: output
+
+ - name: Ensure Deployment creation is idempotent
+ assert:
+ that: not output.changed
+
+ ### Type tests
+ - name: Create a namespace from a string
+ k8s:
+ definition: |+
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing1
+
+ ### https://github.com/ansible-collections/community.kubernetes/issues/111
+ - set_fact:
+ api_groups: "{{ lookup('kubernetes.core.k8s', cluster_info='api_groups') }}"
+
+ - debug:
+ var: api_groups
+
+ - name: Namespace should exist
+ k8s_info:
+ kind: Namespace
+ api_version: v1
+ name: testing1
+ register: k8s_info_testing1
+ failed_when: not k8s_info_testing1.resources or k8s_info_testing1.resources[0].status.phase != "Active"
+
+ - name: Create resources from a multidocument yaml string
+ k8s:
+ definition: |+
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing2
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing3
+
+ - name: Lookup namespaces
+ k8s_info:
+ api_version: v1
+ kind: Namespace
+ name: "{{ item }}"
+ loop:
+ - testing2
+ - testing3
+ register: k8s_namespaces
+
+ - name: Resources should exist
+ assert:
+ that: item.resources[0].status.phase == 'Active'
+ loop: "{{ k8s_namespaces.results }}"
+
+ - name: Delete resources from a multidocument yaml string
+ k8s:
+ state: absent
+ definition: |+
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing2
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing3
+
+ - name: Lookup namespaces
+ k8s_info:
+ api_version: v1
+ kind: Namespace
+ name: "{{ item }}"
+ loop:
+ - testing2
+ - testing3
+ register: k8s_namespaces
+
+ - name: Resources should not exist
+ assert:
+ that:
+ - not item.resources or item.resources[0].status.phase == "Terminating"
+ loop: "{{ k8s_namespaces.results }}"
+
+ - name: Create resources from a list
+ k8s:
+ definition:
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing4
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing5
+
+ - name: Lookup namespaces
+ k8s_info:
+ api_version: v1
+ kind: Namespace
+ name: "{{ item }}"
+ loop:
+ - testing4
+ - testing5
+ register: k8s_namespaces
+
+ - name: Resources should exist
+ assert:
+ that: item.resources[0].status.phase == 'Active'
+ loop: "{{ k8s_namespaces.results }}"
+
+ - name: Delete resources from a list
+ k8s:
+ state: absent
+ definition:
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing4
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing5
+
+ - name: Get info about terminating resources
+ k8s_info:
+ api_version: v1
+ kind: Namespace
+ name: "{{ item }}"
+ loop:
+ - testing4
+ - testing5
+ register: k8s_info
+
+ - name: Ensure resources are terminating if still in results
+ assert:
+ that: not item.resources or item.resources[0].status.phase == "Terminating"
+ loop: "{{ k8s_info.results }}"
+
+ - name: Create resources from a yaml string ending with ---
+ k8s:
+ definition: |+
+ ---
+ kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing6
+ ---
+
+ - name: Namespace should exist
+ k8s_info:
+ kind: Namespace
+ api_version: v1
+ name: testing6
+ 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 }}"
+
+ # test setting module defaults for kubernetes.core.k8s_info
+ - block:
+ - name: Create a namespace
+ kubernetes.core.k8s:
+ name: test-namespace-module-defaults
+ kind: Namespace
+ register: output
+
+ - name: Create a ConfigMap
+ kubernetes.core.k8s:
+ kind: ConfigMap
+ name: test-configmap-1
+ definition:
+ data:
+ key1: value1
+
+ - name: Create another ConfigMap
+ kubernetes.core.k8s:
+ kind: ConfigMap
+ name: test-configmap-2
+ definition:
+ data:
+ key2: value2
+
+ - name: Get list of all ConfigMaps in namespace specified in module_defaults
+ kubernetes.core.k8s_info:
+ kind: ConfigMap
+ register: configmap_info
+
+ - name: assert that the ConfigMaps are created in and info is retrieved for namespace specified in module_defaults
+ assert:
+ that:
+ - configmap_info.resources[1].metadata.name == "test-configmap-1"
+ - configmap_info.resources[1].metadata.namespace == "test-namespace-module-defaults"
+ - configmap_info.resources[2].metadata.name == "test-configmap-2"
+ - configmap_info.resources[2].metadata.namespace == "test-namespace-module-defaults"
+
+ module_defaults:
+ group/kubernetes.core.k8s:
+ namespace: test-namespace-module-defaults
+
+ when: ansible_version.full is version("2.12", ">=")
+
+ always:
+ - name: Delete all namespaces
+ k8s:
+ state: absent
+ definition:
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing1
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing2
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing3
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing4
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing5
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: testing6
+ - kind: Namespace
+ apiVersion: v1
+ metadata:
+ name: test-namespace-module-defaults
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/aliases
new file mode 100644
index 00000000..c7783971
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/aliases
@@ -0,0 +1 @@
+time=142
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/defaults/main.yml
new file mode 100644
index 00000000..d0c74f81
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "garbage"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/tasks/main.yml
new file mode 100644
index 00000000..a2f60c8a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_gc/tasks/main.yml
@@ -0,0 +1,236 @@
+---
+- vars:
+ gc_namespace: "{{ test_namespace }}"
+ gc_name: garbage-job
+ # This is a job definition that runs for 10 minutes and won't gracefully
+ # shutdown. It allows us to test foreground vs background deletion.
+ job_definition:
+ apiVersion: batch/v1
+ kind: Job
+ metadata:
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ spec:
+ template:
+ metadata:
+ labels:
+ job: gc
+ spec:
+ containers:
+ - name: "{{ gc_name }}"
+ image: busybox
+ command:
+ - sleep
+ - "600"
+ restartPolicy: Never
+
+ block:
+ - name: Add a job
+ k8s:
+ definition: "{{ job_definition }}"
+
+ - name: Wait Job's pod
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: wait_job
+ until: wait_job.resources
+ retries: 5
+ delay: 10
+
+ - name: Wait job's pod running
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ name: "{{ wait_job.resources[0].metadata.name }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: job
+
+ - name: Assert job's pod is running
+ assert:
+ that: job.resources[0].status.phase == "Running"
+
+ - name: Delete job in foreground
+ k8s:
+ kind: Job
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ delete_options:
+ propagationPolicy: Foreground
+
+ - name: Test job's pod does not exist
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: job
+
+ - name: Assert job's pod does not exist
+ assert:
+ that: not job.resources
+
+ - name: Add a job
+ k8s:
+ definition: "{{ job_definition }}"
+
+ - name: Wait Job's pod
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: wait_job
+ until: wait_job.resources
+ retries: 5
+ delay: 10
+
+ - name: Wait job's pod running
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ name: "{{ wait_job.resources[0].metadata.name }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: job
+
+ - name: Assert job's pod is running
+ assert:
+ that: job.resources[0].status.phase == "Running"
+
+ - name: Delete job in background
+ k8s:
+ kind: Job
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ delete_options:
+ propagationPolicy: "Background"
+
+ # The default grace period is 30s so this pod should still be running.
+ - name: Test job's pod exists
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: job
+
+ - name: Assert job's pod still running
+ assert:
+ that: job.resources[0].status.phase == "Running"
+
+ - name: Add a job
+ k8s:
+ definition: "{{ job_definition }}"
+
+ - name: Wait Job's pod
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: wait_job
+ until: wait_job.resources
+ retries: 5
+ delay: 10
+
+ - name: Wait job's pod running
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ name: "{{ wait_job.resources[0].metadata.name }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: job
+
+ - name: Assert job's pod is running
+ assert:
+ that: job.resources[0].status.phase == "Running"
+
+ - name: Orphan the job's pod
+ k8s:
+ kind: Job
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ delete_options:
+ propagationPolicy: "Orphan"
+
+ - name: Ensure grace period has expired
+ pause:
+ seconds: 60
+
+ - name: Test that job's pod is still running
+ k8s_info:
+ kind: Pod
+ namespace: "{{ gc_namespace }}"
+ label_selectors:
+ - "job=gc"
+ register: job
+
+ - name: Assert job's pod is still running
+ assert:
+ that: job.resources[0].status.phase == "Running"
+
+ - name: Add a job
+ k8s:
+ definition: "{{ job_definition }}"
+ register: job
+
+ - name: Delete a job with failing precondition
+ k8s:
+ kind: Job
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ state: absent
+ delete_options:
+ preconditions:
+ uid: not-a-valid-uid
+ ignore_errors: yes
+ register: result
+
+ - name: Assert that deletion failed
+ assert:
+ that: result is failed
+
+ - name: Delete a job using a valid precondition
+ k8s:
+ kind: Job
+ name: "{{ gc_name }}"
+ namespace: "{{ gc_namespace }}"
+ state: absent
+ delete_options:
+ preconditions:
+ uid: "{{ job.result.metadata.uid }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - name: Check that job is deleted
+ k8s_info:
+ kind: Job
+ namespace: "{{ gc_namespace }}"
+ name: "{{ gc_name }}"
+ register: job
+
+ - name: Assert job is deleted
+ assert:
+ that: not job.resources
+
+ always:
+ - name: Delete namespace
+ k8s:
+ kind: Namespace
+ name: "{{ gc_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/aliases
new file mode 100644
index 00000000..07f47a63
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/aliases
@@ -0,0 +1,3 @@
+time=36
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/tasks/main.yml
new file mode 100644
index 00000000..081de920
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_generate_name/tasks/main.yml
@@ -0,0 +1,188 @@
+- block:
+ - set_fact:
+ k8s_wait_timeout: 400
+ pod_00:
+ apiVersion: v1
+ kind: Pod
+ spec:
+ containers:
+ - name: py-container
+ image: python:3.7-alpine
+ imagePullPolicy: IfNotPresent
+ command:
+ - /bin/sh
+ - -c
+ - while true;do date;sleep 5; done
+ pod_01:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ generateName: pod-
+ spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: IfNotPresent
+ name: py-container
+
+ - name: Create namespace using generateName
+ k8s:
+ definition:
+ kind: Namespace
+ metadata:
+ generateName: "test-"
+ labels:
+ ansible: test
+ register: result
+
+ - set_fact:
+ namespace: "{{ result.result.metadata.name }}"
+
+ - name: Create Pod without name
+ k8s:
+ namespace: "{{ namespace }}"
+ definition: "{{ pod_00 }}"
+ register: result
+ ignore_errors: true
+
+ - name: assert pod creation failed
+ assert:
+ that:
+ - result is failed
+ - "'name or generateName is required' in result.msg"
+
+ - name: create pod using name parameter should succeed
+ k8s:
+ namespace: "{{ namespace }}"
+ definition: "{{ pod_00 }}"
+ name: pod-01
+
+ - name: list Pod for namespace
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ register: pods
+
+ - name: assert pod has been created
+ assert:
+ that:
+ - "{{ pods.resources | length == 1 }}"
+
+ - name: create pod using generate_name parameter should succeed
+ k8s:
+ namespace: "{{ namespace }}"
+ definition: "{{ pod_00 }}"
+ generate_name: pod-
+
+ - name: list Pod for namespace
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ register: pods
+
+ - name: assert pod has been created
+ assert:
+ that:
+ - "{{ pods.resources | length == 2 }}"
+
+ - name: create pod using metadata.generateName parameter should succeed
+ k8s:
+ namespace: "{{ namespace }}"
+ definition: "{{ pod_01 }}"
+
+ - name: list Pod for namespace
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ register: pods
+
+ - name: assert pod has been created
+ assert:
+ that:
+ - "{{ pods.resources | length == 3 }}"
+
+ - name: create object using metadata.generateName should support wait option
+ k8s:
+ namespace: "{{ namespace }}"
+ definition:
+ apiVersion: apps/v1
+ kind: StatefulSet
+ metadata:
+ generateName: test-
+ spec:
+ selector:
+ matchLabels:
+ app: nginx
+ serviceName: "nginx"
+ replicas: 3
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ terminationGracePeriodSeconds: 10
+ containers:
+ - name: nginx
+ image: k8s.gcr.io/nginx-slim:0.8
+ ports:
+ - containerPort: 80
+ name: web
+ wait: yes
+ wait_sleep: 3
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - name: Create ConfigMap using generateName
+ kubernetes.core.k8s:
+ kind: ConfigMap
+ namespace: "{{ namespace }}"
+ generate_name: cmap-
+ append_hash: yes
+ register: config
+
+ - name: assert that configmap has been created using generateName
+ assert:
+ that:
+ - "config.result.metadata.name.startswith('cmap-')"
+
+ - name: Create Pod with failing container
+ kubernetes.core.k8s:
+ namespace: "{{ namespace }}"
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: pod1
+ spec:
+ containers:
+ - image: adslfkjadslfkjadslkfjsadf
+ name: non-existent-container-image
+
+ - name: Create second Pod using wait (it should not wait for the first container)
+ kubernetes.core.k8s:
+ namespace: "{{ namespace }}"
+ generate_name: "pod2-"
+ definition:
+ apiVersion: v1
+ kind: Pod
+ spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: c0
+ wait: yes
+ wait_timeout: 10
+
+ always:
+ - name: Delete namespace
+ k8s:
+ kind: Namespace
+ name: "{{ namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/aliases
new file mode 100644
index 00000000..0fca1500
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/aliases
@@ -0,0 +1,3 @@
+time=20m
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/defaults/main.yml
new file mode 100644
index 00000000..66ecab29
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/defaults/main.yml
@@ -0,0 +1,5 @@
+---
+test_namespace:
+ - wait
+ - python-api-caching
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/api-server-caching.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/api-server-caching.yml
new file mode 100644
index 00000000..43e835fd
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/api-server-caching.yml
@@ -0,0 +1,91 @@
+---
+- name: create temporary directory for tests
+ tempfile:
+ state: directory
+ suffix: .test
+ register: _testdir
+
+- block:
+ - name: Create kubernetes secret
+ k8s:
+ namespace: '{{ api_namespace }}'
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: '{{ test_secret }}'
+ type: Opaque
+ stringData:
+ foo: bar
+
+ - name: save default kubeconfig
+ copy:
+ src: ~/.kube/config
+ dest: '{{ _testdir.path }}/config'
+
+ - name: create bad kubeconfig
+ copy:
+ src: '{{ _testdir.path }}/config'
+ dest: '{{ _testdir.path }}/badconfig'
+
+ - name: Remove certificate-data from badconfig
+ ansible.builtin.lineinfile:
+ path: '{{ _testdir.path }}/badconfig'
+ regexp: "(.*)certificate-authority-data(.*)"
+ state: absent
+
+ - name: Delete default config
+ file:
+ state: absent
+ path: ~/.kube/config
+
+ - name: Check for existing cluster namespace with good kubeconfig
+ k8s_info:
+ api_version: v1
+ kind: Secret
+ name: '{{ test_secret }}'
+ namespace: '{{ api_namespace }}'
+ kubeconfig: '{{ _testdir.path }}/config'
+ register: result_good_config
+
+ - name: Check for existing cluster namespace with bad kubeconfig
+ k8s_info:
+ api_version: v1
+ kind: Secret
+ name: '{{ test_secret }}'
+ namespace: '{{ api_namespace }}'
+ kubeconfig: '{{ _testdir.path }}/badconfig'
+ register: result_bad_config
+ ignore_errors: true
+
+ - name: Ensure task has failed with proper message
+ assert:
+ that:
+ - result_good_config is successful
+ - result_good_config.resources | length == 1
+ - result_bad_config is failed
+ - '"certificate verify failed" in result_bad_config.msg'
+
+ vars:
+ api_namespace: "{{ test_namespace[1] }}"
+ test_secret: "my-secret"
+
+ always:
+
+ - name: restore default kubeconfig
+ copy:
+ dest: ~/.kube/config
+ src: '{{ _testdir.path }}/config'
+
+ - name: Delete namespace
+ k8s:
+ kind: namespace
+ name: "{{ api_namespace }}"
+ state: absent
+ ignore_errors: true
+
+ - name: delete temporary directory
+ file:
+ state: absent
+ path: '{{ _testdir.path }}'
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/main.yml
new file mode 100644
index 00000000..f15274a5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/main.yml
@@ -0,0 +1,5 @@
+---
+- include_tasks: "tasks/{{ item }}.yml"
+ with_items:
+ - wait
+ - api-server-caching
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/wait.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/wait.yml
new file mode 100644
index 00000000..2608f820
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_info/tasks/wait.yml
@@ -0,0 +1,238 @@
+---
+- block:
+ - set_fact:
+ wait_namespace: "{{ test_namespace[0] }}"
+ multi_pod_one: multi-pod-1
+ multi_pod_two: multi-pod-2
+
+ - name: Add a simple pod with initContainer
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ k8s_pod_name }}"
+ namespace: "{{ wait_namespace }}"
+ spec:
+ initContainers:
+ - name: init-01
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 20']
+ containers:
+ - name: utilitypod-01
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 360']
+
+ - name: Wait and gather information about new pod
+ k8s_info:
+ name: "{{ k8s_pod_name }}"
+ kind: Pod
+ namespace: "{{ wait_namespace }}"
+ wait: yes
+ wait_sleep: 5
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: wait_info
+
+ - name: Assert that pod creation succeeded
+ assert:
+ that:
+ - wait_info is successful
+ - not wait_info.changed
+ - wait_info.resources[0].status.phase == "Running"
+
+ - name: Remove Pod
+ k8s:
+ api_version: v1
+ kind: Pod
+ name: "{{ k8s_pod_name }}"
+ namespace: "{{ wait_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ ignore_errors: yes
+ register: short_wait_remove_pod
+
+ - name: Check if pod is removed
+ assert:
+ that:
+ - short_wait_remove_pod is successful
+ - short_wait_remove_pod.changed
+
+ - name: Create multiple pod with initContainer
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ labels:
+ run: multi-box
+ name: "{{ multi_pod_one }}"
+ namespace: "{{ wait_namespace }}"
+ spec:
+ initContainers:
+ - name: init-01
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 25']
+ containers:
+ - name: multi-pod-01
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 360']
+
+ - name: Create another pod with same label as previous pod
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ labels:
+ run: multi-box
+ name: "{{ multi_pod_two }}"
+ namespace: "{{ wait_namespace }}"
+ spec:
+ initContainers:
+ - name: init-02
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 25']
+ containers:
+ - name: multi-pod-02
+ image: python:3.7-alpine
+ command: ['sh', '-c', 'sleep 360']
+
+ - name: Wait and gather information about new pods
+ k8s_info:
+ kind: Pod
+ namespace: "{{ wait_namespace }}"
+ wait: yes
+ wait_sleep: 5
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ label_selectors:
+ - run == multi-box
+ register: wait_info
+
+ - name: Assert that pod creation succeeded
+ assert:
+ that:
+ - wait_info is successful
+ - not wait_info.changed
+ - wait_info.resources[0].status.phase == "Running"
+ - wait_info.resources[1].status.phase == "Running"
+
+ - name: "Remove Pod {{ multi_pod_one }}"
+ k8s:
+ api_version: v1
+ kind: Pod
+ name: "{{ multi_pod_one }}"
+ namespace: "{{ wait_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ ignore_errors: yes
+ register: multi_pod_one_remove
+
+ - name: "Check if {{ multi_pod_one }} pod is removed"
+ assert:
+ that:
+ - multi_pod_one_remove is successful
+ - multi_pod_one_remove.changed
+
+ - name: "Remove Pod {{ multi_pod_two }}"
+ k8s:
+ api_version: v1
+ kind: Pod
+ name: "{{ multi_pod_two }}"
+ namespace: "{{ wait_namespace }}"
+ state: absent
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ ignore_errors: yes
+ register: multi_pod_two_remove
+
+ - name: "Check if {{ multi_pod_two }} pod is removed"
+ assert:
+ that:
+ - multi_pod_two_remove is successful
+ - multi_pod_two_remove.changed
+
+ - name: "Look for existing API"
+ k8s_info:
+ api_version: apps/v1
+ kind: Deployment
+ register: existing_api
+
+ - name: Check if we informed the user the api does exist
+ assert:
+ that:
+ - existing_api.api_found
+
+ - name: "Look for non-existent API"
+ k8s_info:
+ api_version: pleasedonotcreatethisresource.example.com/v7
+ kind: DoesNotExist
+ register: dne_api
+
+ - name: Check if we informed the user the api does not exist
+ assert:
+ that:
+ - not dne_api.resources
+ - not dne_api.api_found
+
+ - name: Start timer
+ set_fact:
+ start: "{{ lookup('pipe', 'date +%s') }}"
+
+ - name: Wait for non-existent pod to be created
+ k8s_info:
+ kind: Pod
+ name: does-not-exist
+ namespace: "{{ wait_namespace }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: result
+
+ - name: Check that module waited
+ assert:
+ that:
+ - "{{ lookup('pipe', 'date +%s') }} - {{ start }} > 30"
+
+ - name: Create simple pod
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: wait-pod-1
+ namespace: "{{ wait_namespace }}"
+ spec:
+ containers:
+ - image: busybox
+ name: busybox
+ command:
+ - /bin/sh
+ - -c
+ - while true; do sleep 5; done
+
+ - name: Wait for multiple non-existent pods to be created
+ k8s_info:
+ kind: Pod
+ namespace: "{{ wait_namespace }}"
+ label_selectors:
+ - thislabel=doesnotexist
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: result
+
+ - name: Assert no pods were found
+ assert:
+ that:
+ - not result.resources
+
+ vars:
+ k8s_pod_name: pod-info-1
+
+ always:
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ wait_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/aliases
new file mode 100644
index 00000000..73984332
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/aliases
@@ -0,0 +1,3 @@
+k8s_json_patch
+k8s
+time=33
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/defaults/main.yml
new file mode 100644
index 00000000..a6c8adb7
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "json-patch"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/tasks/main.yml
new file mode 100644
index 00000000..314f611b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_json_patch/tasks/main.yml
@@ -0,0 +1,172 @@
+- vars:
+ pod: json-patch
+ deployment: json-patch
+ k8s_wait_timeout: 400
+
+ block:
+ - name: Create a simple pod
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ namespace: "{{ test_namespace }}"
+ name: "{{ pod }}"
+ labels:
+ label1: foo
+ spec:
+ containers:
+ - image: busybox:musl
+ name: busybox
+ command:
+ - sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - name: Add a label and replace the image in checkmode
+ kubernetes.core.k8s_json_patch:
+ kind: Pod
+ namespace: "{{ test_namespace }}"
+ name: "{{ pod }}"
+ patch:
+ - op: add
+ path: /metadata/labels/label2
+ value: bar
+ - op: replace
+ path: /spec/containers/0/image
+ value: busybox:glibc
+ check_mode: yes
+ register: result
+ diff: yes
+
+ - name: Assert patch was made
+ assert:
+ that:
+ - 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:
+ kind: Pod
+ name: "{{ pod }}"
+ namespace: "{{ test_namespace }}"
+ register: result
+
+ - name: Assert pod has not changed
+ assert:
+ that:
+ - result.resources[0].metadata.labels.label2 is not defined
+ - result.resources[0].spec.containers[0].image == "busybox:musl"
+
+ - name: Add a label and replace the image
+ kubernetes.core.k8s_json_patch:
+ kind: Pod
+ namespace: "{{ test_namespace }}"
+ name: "{{ pod }}"
+ patch:
+ - op: add
+ path: /metadata/labels/label2
+ value: bar
+ - op: replace
+ path: /spec/containers/0/image
+ value: busybox:glibc
+ register: result
+ diff: no
+
+ - name: Assert patch was made
+ assert:
+ that:
+ - result.changed
+ - result.diff is not defined
+
+ - name: Describe pod
+ kubernetes.core.k8s_info:
+ kind: Pod
+ name: "{{ pod }}"
+ namespace: "{{ test_namespace }}"
+ register: result
+
+ - name: Assert that both patch operations have been applied
+ assert:
+ that:
+ - result.resources[0].metadata.labels.label2 == "bar"
+ - result.resources[0].spec.containers[0].image == "busybox:glibc"
+
+ - name: Apply the same patch to the pod
+ kubernetes.core.k8s_json_patch:
+ kind: Pod
+ namespace: "{{ test_namespace }}"
+ name: "{{ pod }}"
+ patch:
+ - op: add
+ path: /metadata/labels/label2
+ value: bar
+ - op: replace
+ path: /spec/containers/0/image
+ value: busybox:glibc
+ register: result
+
+ - name: Assert that no changes were made
+ assert:
+ that:
+ - not result.changed
+
+ - name: Create a simple deployment
+ kubernetes.core.k8s:
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ wait: yes
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ namespace: "{{ test_namespace }}"
+ name: "{{ deployment }}"
+ labels:
+ name: "{{ deployment }}"
+ spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: busybox
+ template:
+ metadata:
+ labels:
+ app: busybox
+ spec:
+ containers:
+ - name: busybox
+ image: busybox
+ command:
+ - sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+
+ - name: Apply patch and wait for deployment to be ready
+ kubernetes.core.k8s_json_patch:
+ kind: Deployment
+ namespace: "{{ test_namespace }}"
+ name: "{{ deployment }}"
+ patch:
+ - op: replace
+ path: /spec/replicas
+ value: 3
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: result
+
+ - name: Assert all replicas are available
+ assert:
+ that:
+ - result.result.status.availableReplicas == 3
+
+ always:
+ - name: Ensure namespace has been deleted
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/aliases
new file mode 100644
index 00000000..1c5bbb1b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/aliases
@@ -0,0 +1,3 @@
+time=184
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/defaults/main.yml
new file mode 100644
index 00000000..d50c8fe2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "label-selectors"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/tasks/main.yml
new file mode 100644
index 00000000..af43e7ea
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_label_selectors/tasks/main.yml
@@ -0,0 +1,652 @@
+---
+- block:
+ - set_fact:
+ selector_namespace: "{{ test_namespace }}"
+ 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"
+
+ # 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: "{{ k8s_wait_timeout | default(omit) }}"
+
+ - 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: "{{ k8s_wait_timeout | default(omit) }}"
+ 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: "{{ k8s_wait_timeout | default(omit) }}"
+ 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
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/aliases
new file mode 100644
index 00000000..c0d2100e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/aliases
@@ -0,0 +1,3 @@
+time=22
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/defaults/main.yml
new file mode 100644
index 00000000..2ad3cd01
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "lists"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/tasks/main.yml
new file mode 100644
index 00000000..ab9a134f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_lists/tasks/main.yml
@@ -0,0 +1,141 @@
+---
+- block:
+ - block:
+ - name: Create configmaps
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMapList
+ items: '{{ configmaps }}'
+
+ - name: Get ConfigMaps
+ k8s_info:
+ api_version: v1
+ kind: ConfigMap
+ namespace: "{{ test_namespace }}"
+ label_selectors:
+ - app=test
+ register: cms
+
+ - name: All three configmaps should exist
+ assert:
+ that: item.data.a is defined
+ with_items: '{{ cms.resources }}'
+
+ - name: Delete configmaps
+ k8s:
+ state: absent
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: ConfigMapList
+ items: '{{ configmaps }}'
+
+ - name: Get ConfigMaps
+ k8s_info:
+ api_version: v1
+ kind: ConfigMap
+ namespace: "{{ test_namespace }}"
+ label_selectors:
+ - app=test
+ register: cms
+
+ - name: All three configmaps should not exist
+ assert:
+ that: not cms.resources
+ vars:
+ configmaps:
+ - metadata:
+ name: list-example-1
+ labels:
+ app: test
+ data:
+ a: first
+ - metadata:
+ name: list-example-2
+ labels:
+ app: test
+ data:
+ a: second
+ - metadata:
+ name: list-example-3
+ labels:
+ app: test
+ data:
+ a: third
+
+ - block:
+ - name: Create list of arbitrary resources
+ k8s:
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: List
+ namespace: "{{ test_namespace }}"
+ items: '{{ resources }}'
+
+ - name: Get the created resources
+ k8s_info:
+ api_version: '{{ item.apiVersion }}'
+ kind: '{{ item.kind }}'
+ namespace: "{{ test_namespace }}"
+ name: '{{ item.metadata.name }}'
+ register: list_resources
+ with_items: '{{ resources }}'
+
+ - name: All resources should exist
+ assert:
+ that: ((list_resources.results | sum(attribute="resources", start=[])) | length) == (resources | length)
+
+ - name: Delete list of arbitrary resources
+ k8s:
+ state: absent
+ namespace: "{{ test_namespace }}"
+ definition:
+ apiVersion: v1
+ kind: List
+ namespace: "{{ test_namespace }}"
+ items: '{{ resources }}'
+
+ - name: Get the resources
+ k8s_info:
+ api_version: '{{ item.apiVersion }}'
+ kind: '{{ item.kind }}'
+ namespace: "{{ test_namespace }}"
+ name: '{{ item.metadata.name }}'
+ register: list_resources
+ with_items: '{{ resources }}'
+
+ - name: The resources should not exist
+ assert:
+ that: not ((list_resources.results | sum(attribute="resources", start=[])) | length)
+ vars:
+ resources:
+ - apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: list-example-4
+ data:
+ key: value
+ - apiVersion: v1
+ kind: Service
+ metadata:
+ name: list-example-svc
+ labels:
+ app: test
+ spec:
+ selector:
+ app: test
+ ports:
+ - protocol: TCP
+ targetPort: 8000
+ name: port-8000-tcp
+ port: 8000
+ always:
+ - name: Remove "{{ test_namespace }}" namespace
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/aliases
new file mode 100644
index 00000000..38be79f8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/aliases
@@ -0,0 +1,2 @@
+k8s_log
+time=27
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/defaults/main.yml
new file mode 100644
index 00000000..e5892521
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "k8s-log"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/tasks/main.yml
new file mode 100644
index 00000000..fd312e3b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_log/tasks/main.yml
@@ -0,0 +1,247 @@
+---
+- block:
+ - name: Retrieve log from unexisting Pod
+ k8s_log:
+ namespace: "{{ test_namespace }}"
+ name: "this_pod_does_exist"
+ ignore_errors: true
+ register: fake_pod
+
+ - name: Assert that task failed with proper message
+ assert:
+ that:
+ - fake_pod is failed
+ - 'fake_pod.msg == "Pod {{ test_namespace }}/this_pod_does_exist not found."'
+
+ - name: create hello-world deployment
+ k8s:
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: hello-world
+ namespace: "{{ test_namespace }}"
+ spec:
+ selector:
+ matchLabels:
+ app: hello-world
+ template:
+ metadata:
+ labels:
+ app: hello-world
+ spec:
+ containers:
+ - image: busybox
+ name: hello-world
+ command: ['sh']
+ args: ['-c', 'while true ; do echo "hello world" && sleep 10 ; done']
+ restartPolicy: Always
+
+ - name: retrieve the log by providing the deployment
+ k8s_log:
+ api_version: apps/v1
+ kind: Deployment
+ namespace: "{{ test_namespace }}"
+ name: hello-world
+ register: deployment_log
+
+ - name: verify that the log can be retrieved via the deployment
+ assert:
+ that:
+ - "'hello world' in deployment_log.log"
+ - item == 'hello world' or item == ''
+ with_items: '{{ deployment_log.log_lines }}'
+
+ - name: retrieve the log with a label selector
+ k8s_log:
+ namespace: "{{ test_namespace }}"
+ label_selectors:
+ - 'app=hello-world'
+ register: label_selector_log
+
+ - name: verify that the log can be retrieved via the label
+ assert:
+ that:
+ - "'hello world' in label_selector_log.log"
+ - item == 'hello world' or item == ''
+ with_items: '{{ label_selector_log.log_lines }}'
+
+ - name: get the hello-world pod
+ k8s_info:
+ kind: Pod
+ namespace: "{{ test_namespace }}"
+ label_selectors:
+ - 'app=hello-world'
+ register: k8s_log_pods
+
+ - name: retrieve the log directly with the pod name
+ k8s_log:
+ namespace: "{{ test_namespace }}"
+ name: '{{ k8s_log_pods.resources.0.metadata.name }}'
+ register: pod_log
+
+ - name: verify that the log can be retrieved via the pod name
+ assert:
+ that:
+ - "'hello world' in pod_log.log"
+ - item == 'hello world' or item == ''
+ with_items: '{{ pod_log.log_lines }}'
+
+ - name: Create a job that calculates 7
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ wait_condition:
+ type: Complete
+ status: 'True'
+ definition:
+ apiVersion: batch/v1
+ kind: Job
+ metadata:
+ name: int-log
+ namespace: "{{ test_namespace }}"
+ spec:
+ template:
+ spec:
+ containers:
+ - name: busybox
+ image: busybox
+ command: ["echo", "7"]
+ restartPolicy: Never
+ backoffLimit: 4
+
+ - name: retrieve logs from the job
+ k8s_log:
+ api_version: batch/v1
+ kind: Job
+ namespace: "{{ test_namespace }}"
+ name: int-log
+ register: job_logs
+
+ - name: verify the log was successfully retrieved
+ assert:
+ that: job_logs.log_lines[0] == "7"
+
+ - name: create a job that has 10 log lines
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ wait_condition:
+ type: Complete
+ status: 'True'
+ definition:
+ apiVersion: batch/v1
+ kind: Job
+ metadata:
+ name: multiline-log
+ namespace: "{{ test_namespace }}"
+ spec:
+ template:
+ spec:
+ containers:
+ - name: busybox
+ image: busybox
+ command: ['sh']
+ args: ['-c', 'for i in $(seq 0 9); do echo $i; done']
+ restartPolicy: Never
+ backoffLimit: 4
+
+ - name: retrieve last 5 log lines from the job
+ k8s_log:
+ api_version: batch/v1
+ kind: Job
+ namespace: "{{ test_namespace }}"
+ name: multiline-log
+ tail_lines: 5
+ register: tailed_log
+
+ # The log_lines by k8s_log always contain a trailing empty element,
+ # so if "tail"ing 5 lines, the length will be 6.
+ - name: verify that specific number of logs have been retrieved
+ assert:
+ that: tailed_log.log_lines | length == 5 + 1
+
+ # Trying to call module without name and label_selectors
+ - name: Retrieve without neither name nor label_selectors provided
+ k8s_log:
+ namespace: "{{ test_namespace }}"
+ register: noname_log
+ ignore_errors: true
+
+ - name: Ensure task failed
+ assert:
+ that:
+ - noname_log is failed
+ - 'noname_log.msg == "name must be provided for resources that do not support namespaced base url"'
+
+ # Test retrieve all containers logs
+ - name: Create deployments
+ k8s:
+ namespace: "{{ test_namespace }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ wait_condition:
+ type: Complete
+ status: 'True'
+ definition:
+ apiVersion: batch/v1
+ kind: Job
+ metadata:
+ name: multicontainer-log
+ spec:
+ template:
+ spec:
+ containers:
+ - name: p01
+ image: busybox
+ command: ['sh']
+ args: ['-c', 'for i in $(seq 0 9); do echo $i; done']
+ - name: p02
+ image: busybox
+ command: ['sh']
+ args: ['-c', 'for i in $(seq 10 19); do echo $i; done']
+ restartPolicy: Never
+
+ - name: Retrieve logs from all containers
+ k8s_log:
+ api_version: batch/v1
+ kind: Job
+ namespace: "{{ test_namespace }}"
+ name: multicontainer-log
+ all_containers: true
+ register: all_logs
+
+ - name: Retrieve logs from first job
+ k8s_log:
+ api_version: batch/v1
+ kind: Job
+ namespace: "{{ test_namespace }}"
+ name: multicontainer-log
+ container: p01
+ register: log_1
+
+ - name: Retrieve logs from second job
+ k8s_log:
+ api_version: batch/v1
+ kind: Job
+ namespace: "{{ test_namespace }}"
+ name: multicontainer-log
+ container: p02
+ register: log_2
+
+ - name: Validate that log using all_containers=true is the sum of all logs
+ assert:
+ that:
+ - all_logs.log == (log_1.log + log_2.log)
+
+ always:
+ - name: ensure that namespace is removed
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/aliases
new file mode 100644
index 00000000..998e291c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/aliases
@@ -0,0 +1,4 @@
+k8s_service
+k8s
+k8s_scale
+time=40
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/defaults/main.yml
new file mode 100644
index 00000000..24133ba5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/defaults/main.yml
@@ -0,0 +1,64 @@
+---
+test_namespace: "k8s-manifest-url"
+file_server_container_name: "nginx-server"
+file_server_published_port: 30001
+file_server_container_image: "docker.io/nginx"
+
+pod_manifest:
+ file_name: pod.yaml
+ definition: |
+ ---
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: yaml-pod
+ spec:
+ containers:
+ - name: busy
+ image: busybox
+ command:
+ - /bin/sh
+ - -c
+ - while true;do date;sleep 5; done
+
+deployment_manifest:
+ file_name: deployment.yaml
+ definition: |
+ ---
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: nginx-deployment
+ labels:
+ app: nginx
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx
+
+service_manifest:
+ file_name: service.yaml
+ definition: |
+ ---
+ apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ ports:
+ - name: http
+ port: 80
+ selector:
+ app: nginx
+ status:
+ loadBalancer: {}
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/meta/main.yml
new file mode 100644
index 00000000..9963f67e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/tasks/main.yml
new file mode 100644
index 00000000..1d767b61
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_manifest_url/tasks/main.yml
@@ -0,0 +1,132 @@
+- name: check if docker is installed
+ shell: "command -v docker"
+ register: result
+ ignore_errors: true
+
+- block:
+ - name: Check running server
+ shell:
+ cmd: >
+ docker container ps -a
+ -f name={{ file_server_container_name }}
+ --format '{{ '{{' }} .Names {{ '}}' }}'
+ register: server
+
+ - name: Create static file server using on docker
+ block:
+ - name: Create temporary directory for file to server
+ tempfile:
+ state: directory
+ suffix: .manifests
+ register: manifests_dir
+
+ - name: Update directory permissions
+ file:
+ path: "{{ manifests_dir.path }}"
+ mode: 0755
+
+ - name: Create manifests files
+ copy:
+ content: "{{ item.definition }}"
+ dest: "{{ manifests_dir.path }}/{{ item.file_name }}"
+ with_items:
+ - "{{ pod_manifest }}"
+ - "{{ deployment_manifest }}"
+ - "{{ service_manifest }}"
+
+ - name: Create static file server
+ shell:
+ cmd: >
+ docker run
+ --name {{ file_server_container_name }}
+ -p {{ file_server_published_port }}:80
+ -v {{ manifests_dir.path }}:/usr/share/nginx/html:ro
+ -d {{ file_server_container_image }}
+
+ when: server.stdout == ""
+
+ - set_fact:
+ file_server_host: "http://127.0.0.1:{{ file_server_published_port }}"
+
+ # k8s
+ - name: Create Pod using manifest URL
+ k8s:
+ namespace: "{{ test_namespace }}"
+ src: "{{ file_server_host }}/{{ pod_manifest.file_name }}"
+ wait: true
+
+ - name: Read Pod created
+ k8s_info:
+ kind: Pod
+ namespace: "{{ test_namespace }}"
+ name: "yaml-pod"
+ register: yaml_pod
+
+ - name: Ensure Pod exists
+ assert:
+ that:
+ - yaml_pod.resources | length == 1
+
+ # k8s_scale
+ - name: Create Deployment using manifest URL
+ k8s:
+ namespace: "{{ test_namespace }}"
+ src: "{{ file_server_host }}/{{ deployment_manifest.file_name }}"
+ wait: true
+
+ - name: Scale deployment using manifest URL
+ k8s_scale:
+ namespace: "{{ test_namespace }}"
+ src: "{{ file_server_host }}/{{ deployment_manifest.file_name }}"
+ replicas: 1
+ current_replicas: 3
+ wait: true
+ register: scale
+
+ - name: Read deployment
+ k8s_info:
+ kind: Deployment
+ version: apps/v1
+ namespace: "{{ test_namespace }}"
+ name: "nginx-deployment"
+ register: deployment
+
+ - name: Ensure number of replicas has been set as requested
+ assert:
+ that:
+ - scale is changed
+ - deployment.resources | length == 1
+ - deployment.resources.0.status.replicas == 1
+
+ # k8s_service
+ - name: Create service from manifest URL
+ k8s_service:
+ name: "myservice"
+ namespace: "{{ test_namespace }}"
+ src: "{{ file_server_host }}/{{ service_manifest.file_name }}"
+ register: svc
+
+ - assert:
+ that:
+ - svc is changed
+
+ always:
+ - name: Delete namespace
+ k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+ ignore_errors: true
+
+ - name: Delete static file server
+ shell: "docker container rm -f {{ file_server_container_name }}"
+ ignore_errors: true
+
+ - name: Delete temporary directory
+ file:
+ state: absent
+ path: "{{ manifests_dir.path }}"
+ ignore_errors: true
+ when: manifests_dir is defined
+
+ when: result.rc == 0
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/aliases
new file mode 100644
index 00000000..b961a8b1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/aliases
@@ -0,0 +1,3 @@
+time=19
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/defaults/main.yml
new file mode 100644
index 00000000..93c5a7b5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "merge-type"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/tasks/main.yml
new file mode 100644
index 00000000..3c544c76
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_merge_type/tasks/main.yml
@@ -0,0 +1,138 @@
+- block:
+ - name: Define common facts
+ set_fact:
+ k8s_patch_namespace: "{{ test_namespace }}"
+ k8s_strategic_merge: "strategic-merge"
+ k8s_merge: "json-merge"
+ k8s_json: "json-patch"
+
+ # Strategic merge
+ - name: create a simple nginx deployment
+ kubernetes.core.k8s:
+ namespace: "{{ k8s_patch_namespace }}"
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: "{{ k8s_strategic_merge }}"
+ spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: "{{ k8s_strategic_merge }}-ctr"
+ image: nginx
+ tolerations:
+ - effect: NoSchedule
+ key: dedicated
+ value: "test-strategic-merge"
+
+
+ - name: patch service using strategic merge
+ kubernetes.core.k8s:
+ kind: Deployment
+ namespace: "{{ k8s_patch_namespace }}"
+ name: "{{ k8s_strategic_merge }}"
+ definition:
+ spec:
+ template:
+ spec:
+ containers:
+ - name: "{{ k8s_strategic_merge }}-ctr-2"
+ image: redis
+ register: depl_patch
+
+ - name: validate that resource was patched
+ assert:
+ that:
+ - depl_patch.changed
+
+ - name: describe "{{ k8s_strategic_merge }}" deployment
+ kubernetes.core.k8s_info:
+ kind: Deployment
+ name: "{{ k8s_strategic_merge }}"
+ namespace: "{{ k8s_patch_namespace }}"
+ register: deployment_out
+
+ - name: assert that deployment contains expected images
+ assert:
+ that:
+ - deployment_out.resources[0].spec.template.spec.containers | selectattr('image','equalto','nginx') | list | length == 1
+ - deployment_out.resources[0].spec.template.spec.containers | selectattr('image','equalto','redis') | list | length == 1
+
+ # Json merge
+ - name: create a simple nginx deployment (testing merge patch)
+ kubernetes.core.k8s:
+ namespace: "{{ k8s_patch_namespace }}"
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: "{{ k8s_merge }}"
+ spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: "{{ k8s_merge }}-ctr"
+ image: nginx
+ tolerations:
+ - effect: NoSchedule
+ key: dedicated
+ value: "test-strategic-merge"
+
+
+ - name: patch service using json merge patch
+ kubernetes.core.k8s:
+ kind: Deployment
+ api_version: apps/v1
+ namespace: "{{ k8s_patch_namespace }}"
+ name: "{{ k8s_merge }}"
+ merge_type:
+ - merge
+ definition:
+ spec:
+ template:
+ spec:
+ containers:
+ - name: "{{ k8s_merge }}-ctr-2"
+ image: python
+ register: merge_patch
+
+ - name: validate that resource was patched
+ assert:
+ that:
+ - merge_patch.changed
+
+ - name: describe "{{ k8s_merge }}" deployment
+ kubernetes.core.k8s_info:
+ kind: Deployment
+ name: "{{ k8s_merge }}"
+ namespace: "{{ k8s_patch_namespace }}"
+ register: merge_out
+
+ - name: assert that deployment contains expected images
+ assert:
+ that:
+ - merge_out.resources[0].spec.template.spec.containers | list | length == 1
+ - merge_out.resources[0].spec.template.spec.containers[0].image == 'python'
+
+ always:
+ - name: Ensure namespace has been deleted
+ kubernetes.core.k8s:
+ kind: namespace
+ name: "{{ k8s_patch_namespace }}"
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/aliases
new file mode 100644
index 00000000..106e8b44
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/aliases
@@ -0,0 +1,3 @@
+time=20
+k8s
+k8s_info \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/defaults/main.yml
new file mode 100644
index 00000000..a2ecfe95
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/defaults/main.yml
@@ -0,0 +1,4 @@
+---
+test_namespace:
+ - patched-namespace-1
+ - patched-namespace-2
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/meta/main.yml
new file mode 100644
index 00000000..54561c97
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/tasks/main.yml
new file mode 100644
index 00000000..8846d064
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_patched/tasks/main.yml
@@ -0,0 +1,121 @@
+---
+- block:
+ - set_fact:
+ patch_only_namespace: "{{ test_namespace }}"
+
+ - name: Ensure namespace {{ patch_only_namespace[0] }} exist
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ patch_only_namespace[0] }}"
+ labels:
+ existingLabel: "labelValue"
+ annotations:
+ existingAnnotation: "annotationValue"
+ wait: yes
+
+ - name: Ensure namespace {{ patch_only_namespace[1] }} does not exist
+ kubernetes.core.k8s_info:
+ kind: namespace
+ name: "{{ patch_only_namespace[1] }}"
+ register: second_namespace
+
+ - name: assert that second namespace does not exist
+ assert:
+ that:
+ - second_namespace.resources | length == 0
+
+ - name: apply patch on existing resource
+ kubernetes.core.k8s:
+ state: patched
+ wait: yes
+ definition: |
+ ---
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ patch_only_namespace[0] }}"
+ labels:
+ ansible: patched
+ ---
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ patch_only_namespace[1] }}"
+ labels:
+ ansible: patched
+ register: patch_resource
+
+ - name: assert that patch succeed
+ assert:
+ that:
+ - patch_resource.changed
+ - patch_resource.result.results | selectattr('warnings', 'defined') | list | length == 1
+
+ - name: Ensure namespace {{ patch_only_namespace[0] }} was patched correctly
+ kubernetes.core.k8s_info:
+ kind: namespace
+ name: "{{ patch_only_namespace[0] }}"
+ register: first_namespace
+
+ - name: assert labels are as expected
+ assert:
+ that:
+ - first_namespace.resources[0].metadata.labels.ansible == "patched"
+ - first_namespace.resources[0].metadata.labels.existingLabel == "labelValue"
+ - first_namespace.resources[0].metadata.annotations.existingAnnotation == "annotationValue"
+ - name: Ensure namespace {{ patch_only_namespace[1] }} was not created
+ kubernetes.core.k8s_info:
+ kind: namespace
+ name: "{{ patch_only_namespace[1] }}"
+ register: second_namespace
+
+ - name: assert that second namespace does not exist
+ assert:
+ that:
+ - second_namespace.resources | length == 0
+
+ - name: patch all resources (create if does not exist)
+ kubernetes.core.k8s:
+ state: present
+ definition: |
+ ---
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ patch_only_namespace[0] }}"
+ labels:
+ patch: ansible
+ ---
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ patch_only_namespace[1] }}"
+ labels:
+ patch: ansible
+ wait: yes
+ register: patch_resource
+
+ - name: Ensure namespace {{ patch_only_namespace[1] }} was created
+ kubernetes.core.k8s_info:
+ kind: namespace
+ name: "{{ patch_only_namespace[1] }}"
+ register: second_namespace
+
+ - name: assert that second namespace exist
+ assert:
+ that:
+ - second_namespace.resources | length == 1
+
+ always:
+ - name: Remove namespace
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ item }}"
+ state: absent
+ with_items:
+ - "{{ patch_only_namespace[0] }}"
+ - "{{ patch_only_namespace[1] }}"
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/aliases
new file mode 100644
index 00000000..3330ef71
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/aliases
@@ -0,0 +1,4 @@
+k8s_rollback
+k8s
+k8s_info
+time=16m
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/defaults/main.yml
new file mode 100644
index 00000000..58d8ae17
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "testingrollback"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/tasks/main.yml
new file mode 100644
index 00000000..0706a22e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_rollback/tasks/main.yml
@@ -0,0 +1,301 @@
+---
+- block:
+ - name: Set variables
+ set_fact:
+ namespace: "{{ test_namespace }}"
+ k8s_wait_timeout: 180
+ - name: Create a deployment
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout }}"
+ inline: &deploy
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: nginx-deploy
+ labels:
+ app: nginx
+ namespace: "{{ namespace }}"
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.17
+ ports:
+ - containerPort: 80
+
+
+ - name: Crash the existing deployment
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: 30
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: nginx-deploy
+ labels:
+ app: nginx
+ namespace: "{{ namespace }}"
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.0.23449928384992872784
+ ports:
+ - containerPort: 80
+ ignore_errors: yes
+ register: crash
+
+ - name: Assert that the Deployment failed
+ assert:
+ that:
+ - crash is failed
+
+ - name: Read the deployment
+ k8s_info:
+ kind: Deployment
+ name: nginx-deploy
+ namespace: "{{ namespace }}"
+ register: deployment
+
+ - set_fact:
+ failed_version: "{{ deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision'] }}"
+
+ - name: Rolling Back the crashed deployment (check mode)
+ k8s_rollback:
+ api_version: apps/v1
+ kind: Deployment
+ name: nginx-deploy
+ namespace: "{{ namespace }}"
+ register: result
+ check_mode: yes
+
+ - assert:
+ that:
+ - result is changed
+
+ - name: Read the deployment
+ k8s_info:
+ kind: Deployment
+ name: nginx-deploy
+ namespace: "{{ namespace }}"
+ register: deployment
+
+ - name: Validate that Rollback using check_mode did not changed the Deployment
+ assert:
+ that:
+ - failed_version == deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision']
+
+ - name: Rolling Back the crashed deployment
+ k8s_rollback:
+ api_version: apps/v1
+ kind: Deployment
+ name: nginx-deploy
+ namespace: "{{ namespace }}"
+ register: result
+
+ - name: assert rollback is changed
+ assert:
+ that:
+ - result is changed
+
+ - name: Read the deployment once again
+ k8s_info:
+ kind: Deployment
+ name: nginx-deploy
+ namespace: "{{ namespace }}"
+ register: deployment
+
+ - name: Validate that Rollback changed the Deployment
+ assert:
+ that:
+ - failed_version | int + 1 == deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision'] | int
+
+ - name: Create a DaemonSet
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: 30
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ labels:
+ k8s-app: fluentd-logging
+ spec:
+ selector:
+ matchLabels:
+ name: fluentd-elasticsearch
+ template:
+ metadata:
+ labels:
+ name: fluentd-elasticsearch
+ spec:
+ tolerations:
+ - key: node-role.kubernetes.io/master
+ effect: NoSchedule
+ containers:
+ - name: fluentd-elasticsearch
+ image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
+ resources:
+ limits:
+ memory: 200Mi
+ requests:
+ cpu: 100m
+ memory: 200Mi
+ volumeMounts:
+ - name: varlog
+ mountPath: /var/log
+ - name: varlibdockercontainers
+ mountPath: /var/lib/docker/containers
+ readOnly: true
+ terminationGracePeriodSeconds: 30
+ volumes:
+ - name: varlog
+ hostPath:
+ path: /var/log
+ - name: varlibdockercontainers
+ hostPath:
+ path: /var/lib/docker/containers
+
+ - name: Crash the existing DaemonSet
+ k8s:
+ state: present
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ labels:
+ k8s-app: fluentd-logging
+ spec:
+ selector:
+ matchLabels:
+ name: fluentd-elasticsearch
+ template:
+ metadata:
+ labels:
+ name: fluentd-elasticsearch
+ spec:
+ tolerations:
+ - key: node-role.kubernetes.io/master
+ effect: NoSchedule
+ containers:
+ - name: fluentd-elasticsearch
+ image: quay.io/fluentd_elasticsearch/fluentd:v2734894949
+ resources:
+ limits:
+ memory: 200Mi
+ requests:
+ cpu: 100m
+ memory: 200Mi
+ volumeMounts:
+ - name: varlog
+ mountPath: /var/log
+ - name: varlibdockercontainers
+ mountPath: /var/lib/docker/containers
+ readOnly: true
+ terminationGracePeriodSeconds: 30
+ volumes:
+ - name: varlog
+ hostPath:
+ path: /var/log
+ - name: varlibdockercontainers
+ hostPath:
+ path: /var/lib/docker/containers
+ register: crash
+ ignore_errors: true
+
+ - name: Assert that the Daemonset failed
+ assert:
+ that:
+ - crash is failed
+
+ - name: Read the crashed DaemonSet
+ k8s_info:
+ kind: DaemonSet
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ register: result
+
+ - set_fact:
+ failed_version: "{{ result.resources[0].metadata.annotations['deprecated.daemonset.template.generation'] }}"
+
+ - name: Rolling Back the crashed DaemonSet (check_mode)
+ k8s_rollback:
+ api_version: apps/v1
+ kind: DaemonSet
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ register: result
+ check_mode: yes
+
+ - name: Read the DaemonSet
+ k8s_info:
+ kind: DaemonSet
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ register: result
+
+ - name: Validate that Rollback using check_mode did not changed the DaemonSet version
+ assert:
+ that:
+ - failed_version == result.resources[0].metadata.annotations['deprecated.daemonset.template.generation']
+
+ - name: Rolling Back the crashed DaemonSet
+ k8s_rollback:
+ api_version: apps/v1
+ kind: DaemonSet
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ register: result
+
+ - name: assert rollback is changed
+ assert:
+ that:
+ - result is changed
+
+ - name: Read the DaemonSet
+ k8s_info:
+ kind: DaemonSet
+ name: fluentd-elasticsearch
+ namespace: "{{ namespace }}"
+ register: result
+
+ - name: Validate that Rollback changed the Daemonset version
+ assert:
+ that:
+ - failed_version | int + 1 == result.resources[0].metadata.annotations['deprecated.daemonset.template.generation'] | int
+
+ always:
+ - name: Delete {{ namespace }} namespace
+ k8s:
+ name: "{{ namespace }}"
+ kind: Namespace
+ api_version: v1
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/aliases
new file mode 100644
index 00000000..5ac9bcf0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/aliases
@@ -0,0 +1,4 @@
+k8s_scale
+k8s
+k8s_info
+time=6m
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/defaults/main.yml
new file mode 100644
index 00000000..5089314b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/defaults/main.yml
@@ -0,0 +1,42 @@
+---
+k8s_pod_metadata:
+ labels:
+ app: "{{ k8s_pod_name }}"
+
+k8s_pod_spec:
+ serviceAccount: "{{ k8s_pod_service_account }}"
+ containers:
+ - image: "{{ k8s_pod_image }}"
+ imagePullPolicy: Always
+ name: "{{ k8s_pod_name }}"
+ command: "{{ k8s_pod_command }}"
+ readinessProbe:
+ initialDelaySeconds: 15
+ exec:
+ command:
+ - /bin/true
+ resources: "{{ k8s_pod_resources }}"
+ ports: "{{ k8s_pod_ports }}"
+ env: "{{ k8s_pod_env }}"
+
+
+k8s_pod_service_account: default
+
+k8s_pod_resources:
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
+
+k8s_pod_command: []
+
+k8s_pod_ports: []
+
+k8s_pod_env: []
+
+k8s_pod_template:
+ metadata: "{{ k8s_pod_metadata }}"
+ spec: "{{ k8s_pod_spec }}"
+
+test_namespace: "scale"
+
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/files/deployment.yaml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/files/deployment.yaml
new file mode 100644
index 00000000..bff04d47
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/files/deployment.yaml
@@ -0,0 +1,50 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: test0
+ labels:
+ app: nginx
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.14.2
+ ports:
+ - containerPort: 80
+ - name: hello
+ image: busybox
+ command: ['sh', '-c', 'echo "Hello, from test0" && sleep 3600']
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: test1
+ labels:
+ app: nginx
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.14.2
+ ports:
+ - containerPort: 80
+ - name: hello
+ image: busybox
+ command: ['sh', '-c', 'echo "Hello, from test1" && sleep 3600']
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/tasks/main.yml
new file mode 100644
index 00000000..eb2107d3
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_scale/tasks/main.yml
@@ -0,0 +1,398 @@
+---
+- block:
+ - set_fact:
+ scale_namespace: "{{ test_namespace }}"
+
+ - name: Add a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ vars:
+ k8s_pod_name: scale-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-green
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ field_selectors:
+ - status.phase=Running
+
+ - name: Scale the deployment (check_mode)
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 0
+ wait: yes
+ register: scale_down
+ check_mode: true
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ field_selectors:
+ - status.phase=Running
+ register: scale_down_deploy_pods
+ ignore_errors: true
+ until: scale_down_deploy_pods.resources | length == 0
+ retries: 6
+ delay: 5
+
+ - name: Ensure the deployment did not changed and pods are still running
+ assert:
+ that:
+ - scale_down is changed
+ - scale_down_deploy_pods.resources | length > 0
+
+ - name: Scale the deployment (check_mode) once again - validate idempotency
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 0
+ wait: yes
+ register: scale_down
+ check_mode: true
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ field_selectors:
+ - status.phase=Running
+ register: scale_down_deploy_pods
+ ignore_errors: true
+ until: scale_down_deploy_pods.resources | length == 0
+ retries: 6
+ delay: 5
+
+ - name: Ensure the deployment did not changed and pods are still running
+ assert:
+ that:
+ - scale_down is changed
+ - scale_down_deploy_pods.resources | length > 0
+
+ - name: Scale the deployment
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 0
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: scale_down
+ diff: true
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ field_selectors:
+ - status.phase=Running
+ register: scale_down_deploy_pods
+ until: scale_down_deploy_pods.resources | length == 0
+ retries: 6
+ delay: 5
+
+ - name: Ensure that scale down took effect
+ assert:
+ that:
+ - scale_down is changed
+ - '"duration" in scale_down'
+ - scale_down.diff
+
+ - name: Scale the deployment once again (idempotency)
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 0
+ wait: yes
+ register: scale_down_idempotency
+ diff: true
+
+ - name: Ensure that scale down did not took effect
+ assert:
+ that:
+ - scale_down_idempotency is not changed
+
+ - name: Reapply the earlier deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ apply: yes
+ vars:
+ k8s_pod_name: scale-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-green
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ register: reapply_after_scale
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ field_selectors:
+ - status.phase=Running
+ register: scale_up_deploy_pods
+
+ - name: Ensure that reapply after scale worked
+ assert:
+ that:
+ - reapply_after_scale is changed
+ - scale_up_deploy_pods.resources | length == 1
+
+ - name: Scale the deployment up
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 2
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: scale_up
+ diff: no
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ field_selectors:
+ - status.phase=Running
+ namespace: "{{ scale_namespace }}"
+ register: scale_up_further_deploy_pods
+
+ - name: Ensure that scale up worked
+ assert:
+ that:
+ - scale_up is changed
+ - '"duration" in scale_up'
+ - scale_up.diff is not defined
+ - scale_up_further_deploy_pods.resources | length == 2
+
+ - name: Don't scale the deployment up
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 2
+ wait: yes
+ register: scale_up_noop
+ diff: no
+
+ - name: Get pods in scale-deploy
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ field_selectors:
+ - status.phase=Running
+ namespace: "{{ scale_namespace }}"
+ register: scale_up_noop_pods
+
+ - name: Ensure that no-op scale up worked
+ assert:
+ that:
+ - scale_up_noop is not changed
+ - scale_up_noop.diff is not defined
+ - scale_up_noop_pods.resources | length == 2
+ - '"duration" in scale_up_noop'
+
+ - name: Scale deployment down without wait
+ k8s_scale:
+ api_version: apps/v1
+ kind: Deployment
+ name: scale-deploy
+ namespace: "{{ scale_namespace }}"
+ replicas: 1
+ wait: no
+ register: scale_down_no_wait
+ diff: true
+
+ - name: Ensure that scale down succeeds
+ k8s_info:
+ kind: Pod
+ label_selectors:
+ - app=scale-deploy
+ namespace: "{{ scale_namespace }}"
+ register: scale_down_no_wait_pods
+ retries: 6
+ delay: 5
+ until: scale_down_no_wait_pods.resources | length == 1
+
+ - name: Ensure that scale down without wait worked
+ assert:
+ that:
+ - scale_down_no_wait is changed
+ - scale_down_no_wait.diff
+ - scale_down_no_wait_pods.resources | length == 1
+
+ # scale multiple resource using label selectors
+ - name: create deployment
+ kubernetes.core.k8s:
+ namespace: "{{ scale_namespace }}"
+ src: files/deployment.yaml
+
+ - name: list deployment
+ kubernetes.core.k8s_info:
+ kind: Deployment
+ namespace: "{{ scale_namespace }}"
+ label_selectors:
+ - app=nginx
+ register: resource
+ - assert:
+ that:
+ - resource.resources | list | length == 2
+
+ - name: scale deployment using resource version
+ kubernetes.core.k8s_scale:
+ replicas: 2
+ kind: Deployment
+ namespace: "{{ scale_namespace }}"
+ resource_version: 0
+ label_selectors:
+ - app=nginx
+ register: scale_out
+
+ - assert:
+ that:
+ - not scale_out.changed
+ - scale_out.results | selectattr('warning', 'defined') | list | length == 2
+
+ - name: scale deployment using current replicas (wrong value)
+ kubernetes.core.k8s_scale:
+ replicas: 2
+ current_replicas: 4
+ kind: Deployment
+ namespace: "{{ scale_namespace }}"
+ label_selectors:
+ - app=nginx
+ register: scale_out
+
+ - assert:
+ that:
+ - not scale_out.changed
+ - scale_out.results | selectattr('warning', 'defined') | list | length == 2
+
+ - name: scale deployment using current replicas (right value)
+ kubernetes.core.k8s_scale:
+ replicas: 2
+ current_replicas: 3
+ kind: Deployment
+ namespace: "{{ scale_namespace }}"
+ label_selectors:
+ - app=nginx
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: scale_out
+
+ - assert:
+ that:
+ - scale_out.changed
+ - scale_out.results | map(attribute='result.status.replicas') | list | unique == [2]
+
+ - name: Create a StatefulSet
+ kubernetes.core.k8s:
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ definition:
+ apiVersion: apps/v1
+ kind: StatefulSet
+ metadata:
+ namespace: "{{ scale_namespace }}"
+ name: scale-set
+ spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: foo
+ template:
+ metadata:
+ labels:
+ app: foo
+ spec:
+ terminationGracePeriodSeconds: 10
+ containers:
+ - image: busybox
+ name: busybox
+ command:
+ - sleep
+ - "600"
+ register: output
+
+ - assert:
+ that:
+ - output.result.status.replicas == 2
+
+ - name: Wait for StatefulSet to scale down to 0
+ kubernetes.core.k8s_scale:
+ kind: StatefulSet
+ api_version: apps/v1
+ name: scale-set
+ namespace: "{{ scale_namespace }}"
+ replicas: 0
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: output
+
+ - assert:
+ that:
+ - output.result.status.replicas == 0
+
+ always:
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ scale_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/aliases
new file mode 100644
index 00000000..b3826ad6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/aliases
@@ -0,0 +1,2 @@
+k8s_taint
+time=81
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/defaults/main.yml
new file mode 100644
index 00000000..7b90849e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "taint"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/tasks/main.yml
new file mode 100644
index 00000000..f1a99314
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_taint/tasks/main.yml
@@ -0,0 +1,443 @@
+---
+- block:
+ - set_fact:
+ namespace: "{{ test_namespace }}"
+ pod_name_1: "pod-1-taint"
+ taint_patch_1:
+ - effect: NoExecute
+ key: "key1"
+ value: "value1"
+ taint_patch_1_update:
+ - effect: NoExecute
+ key: "key1"
+ value: "value_updated"
+ taint_patch_2:
+ - effect: NoSchedule
+ key: "key2"
+ value: "value2"
+ - effect: NoExecute
+ key: "key2"
+ taint_patch_3:
+ - effect: NoSchedule
+ key: "key3"
+ - effect: NoExecute
+ key: "key1"
+
+ - name: List cluster nodes
+ kubernetes.core.k8s_info:
+ kind: node
+ register: _result
+
+ - name: Select a node to taint
+ set_fact:
+ node_to_taint: "{{ _result.resources[0].metadata.name }}"
+
+ - name: Create Pod
+ kubernetes.core.k8s:
+ namespace: '{{ namespace }}'
+ wait: yes
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ pod_name_1 }}"
+ spec:
+ affinity:
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchFields:
+ - key: metadata.name
+ operator: In
+ values:
+ - '{{ node_to_taint }}'
+ containers:
+ - name: c0
+ image: busybox
+ command:
+ - /bin/sh
+ - -c
+ - while true; do date;sleep 5; done
+ terminationGracePeriodSeconds: 10
+ register: _result
+
+ - name: Assert that pod is running on the node
+ assert:
+ that:
+ - _result.result.status.phase == 'Running'
+ - _result.result.spec.nodeName == "{{ node_to_taint }}"
+
+ - name: Taint node (check_mode)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ check_mode: true
+ register: _result
+
+ - name: Assert that node has been tainted (check_mode)
+ assert:
+ that:
+ - _result.changed
+
+ - name: Taint node
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ register: _result
+
+ - name: Assert that node has been tainted
+ assert:
+ that:
+ - _result.changed
+ - "{{ item['effect'] == taint_patch_1[0]['effect'] }}"
+ - "{{ item['key'] == taint_patch_1[0]['key'] }}"
+ loop: "{{ _result.result.spec.taints }}"
+
+ - name: Taint node (idempotency) - (check_mode)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ check_mode: true
+ register: _result
+
+ - name: Assert that node has been tainted (idempotency - no change) - (check_mode)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Taint node (idempotency)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ register: _result
+
+ - name: Assert that node has been tainted (idempotency - no change)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Pause for 30 seconds
+ pause:
+ seconds: 30
+
+ - name: Get Pods
+ kubernetes.core.k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ register: _result
+
+ - name: Assert that Pod has been evicted
+ assert:
+ that:
+ - _result.resources | list | length == 0
+
+ - name: Taint node with replace=true (check_mode)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1}}"
+ replace: true
+ check_mode: true
+ register: _result
+
+ - name: Assert that node has been tainted (replace=true)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Taint node with replace=true
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1}}"
+ replace: true
+ register: _result
+
+ - name: Assert that node has been tainted (replace=true)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Taint again node with replace=true (check_mode)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ replace: true
+ check_mode: true
+ register: _result
+
+ - name: Assert that node has been tainted (replace=true) - (check_mode)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Taint again node with replace=true
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ replace: true
+ register: _result
+
+ - name: Assert that node has been tainted (replace=true)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Update node taints
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1_update }}"
+ register: _result
+
+ - name: Update node taints
+ assert:
+ that:
+ - _result.changed
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | selectattr('value', 'equalto', search_value) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ search_value: "{{ item.value }}"
+ all_taints: "{{ taint_patch_1_update }}"
+ with_items: "{{ _result.result.spec.taints }}"
+
+ - name: Update node taints (idempotence)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1_update }}"
+ register: _result
+
+ - name: Update node taints (idempotence)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Add other taints to node (check_mode)
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_2 }}"
+ check_mode: true
+ register: _result
+
+ - name: Assert that other taints has been added (check_mode)
+ assert:
+ that:
+ - _result.changed
+
+ - name: Add other taints to node
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_2 }}"
+ register: _result
+
+ - name: Assert that other taints has been added
+ assert:
+ that:
+ - _result.changed
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ taint_patch_1 + taint_patch_2 }}"
+ with_items: "{{ _result.result.spec.taints }}"
+
+ - name: Remove taints from node (check_mode)
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ check_mode: true
+ register: _result
+
+ - name: Assert that taint has been removed (check_mode)
+ assert:
+ that:
+ - _result.changed
+
+ - name: Remove taint from node
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ register: _result
+
+ - name: Assert that taint has been removed
+ assert:
+ that:
+ - _result.changed
+
+ - name: Get node taints
+ kubernetes.core.k8s_info:
+ kind: node
+ name: "{{ node_to_taint }}"
+ register: _result
+
+ - name: Assert that taint has been removed
+ assert:
+ that:
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ taint_patch_2 }}"
+ with_items: "{{ _result.resources[0].spec.taints }}"
+
+ - name: Remove taint from node (idempotency)
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ register: _result
+
+ - name: Assert that taint has been removed (idempotency)
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Remove nonexistent taint from node
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_3 }}"
+ register: _result
+
+ - name: Assert taint has been removed
+ assert:
+ that:
+ - not _result.changed
+
+ - name: Re-add taint to node
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_1 }}"
+ register: _result
+
+ - name: Assert that taint has been added
+ assert:
+ that:
+ - _result.changed
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ taint_patch_1 + taint_patch_2 }}"
+ with_items: "{{ _result.result.spec.taints }}"
+
+ - name: Add other taints and update
+ kubernetes.core.k8s_taint:
+ name: "{{ node_to_taint }}"
+ taints: "{{ taint_patch_3 }}"
+ register: _result
+
+ - name: Assert that taints have been added and updated
+ assert:
+ that:
+ - _result.changed
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ taint_patch_3 + taint_patch_2 }}"
+ with_items: "{{ _result.result.spec.taints }}"
+
+ - name: Remove taint using key:effect
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints:
+ - key: "key2"
+ effect: "NoSchedule"
+ register: _result
+
+ - name: Assert that taint using key:effect has been removed
+ assert:
+ that:
+ - _result.changed
+
+ - name: Get node taints
+ kubernetes.core.k8s_info:
+ kind: node
+ name: "{{ node_to_taint }}"
+ register: _result
+
+ - set_fact:
+ left_taint_patch_2: [ "{{ taint_patch_2[1] }}" ]
+
+ - name: Assert that taint using key:effect has been removed
+ assert:
+ that:
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ taint_patch_3 + left_taint_patch_2 }}"
+ with_items: "{{ _result.resources[0].spec.taints }}"
+
+ - name: Remove taint using key
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints:
+ - key: "key3"
+ register: _result
+
+ - name: Assert that taint using key has been removed
+ assert:
+ that:
+ - _result.changed
+
+ - set_fact:
+ left_taint_patch_3: [ "{{ taint_patch_3[1] }}" ]
+
+ - name: Get node taints
+ kubernetes.core.k8s_info:
+ kind: node
+ name: "{{ node_to_taint }}"
+ register: _result
+
+ - name: Assert that taint using key has been removed
+ assert:
+ that:
+ - all_taints | selectattr('key', 'equalto', search_key) | selectattr('effect', 'equalto', search_effect) | list | count > 0
+ vars:
+ search_key: "{{ item.key}}"
+ search_effect: "{{ item.effect }}"
+ all_taints: "{{ left_taint_patch_2 + left_taint_patch_3 }}"
+ with_items: "{{ _result.resources[0].spec.taints }}"
+
+ - name: Remove taints (including non existing ones)
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints:
+ - key: "key1"
+ - key: "key2"
+ - key: "key3"
+
+ - name: Get node taints
+ kubernetes.core.k8s_info:
+ kind: node
+ name: "{{ node_to_taint }}"
+ register: _result
+
+ - name: Assert that taints have been removed
+ assert:
+ that:
+ - _result.resources | selectattr('spec.taints', 'undefined')
+
+ always:
+
+ - name: Delete namespace
+ kubernetes.core.k8s:
+ state: absent
+ kind: Namespace
+ name: "{{ namespace }}"
+ ignore_errors: true
+
+ - name: Remove taints
+ kubernetes.core.k8s_taint:
+ state: absent
+ name: "{{ node_to_taint }}"
+ taints:
+ - key: "key1"
+ - key: "key2"
+ - key: "key3"
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/aliases
new file mode 100644
index 00000000..7a847f3c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/aliases
@@ -0,0 +1,4 @@
+k8s_service
+k8s
+k8s_info
+time=75
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/defaults/main.yml
new file mode 100644
index 00000000..0d62f585
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_namespace: "template-test"
+k8s_wait_timeout: 400
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/tasks/main.yml
new file mode 100644
index 00000000..7aedecea
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/tasks/main.yml
@@ -0,0 +1,305 @@
+---
+- block:
+ - set_fact:
+ template_namespace: "{{ test_namespace }}"
+
+ - name: Check if k8s_service does not inherit parameter
+ kubernetes.core.k8s_service:
+ template: "pod_one.j2"
+ state: present
+ ignore_errors: yes
+ register: r
+
+ - name: Check for expected failures in last tasks
+ assert:
+ that:
+ - r.failed
+ - "'is only a supported parameter for' in r.msg"
+
+ - name: Specify both definition and template
+ kubernetes.core.k8s:
+ state: present
+ template: "pod_one.j2"
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: apply-deploy
+ namespace: "{{ template_namespace }}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name_one }}"
+ vars:
+ k8s_pod_name_one: pod
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+ ignore_errors: yes
+
+ - name: Check if definition and template are mutually exclusive
+ assert:
+ that:
+ - r.failed
+ - "'parameters are mutually exclusive' in r.msg"
+
+ - name: Specify both src and template
+ kubernetes.core.k8s:
+ state: present
+ src: "../templates/pod_one.j2"
+ template: "pod_one.j2"
+ vars:
+ k8s_pod_name_one: pod
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+ ignore_errors: yes
+
+ - name: Check if src and template are mutually exclusive
+ assert:
+ that:
+ - r.failed
+ - "'parameters are mutually exclusive' in r.msg"
+
+ - name: Create pod using template (direct specification)
+ kubernetes.core.k8s:
+ template: "pod_one.j2"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_one: pod-1
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+
+ - name: Assert that pod creation succeeded using template
+ assert:
+ that:
+ - r is successful
+
+ - name: Create pod using template with wrong parameter
+ kubernetes.core.k8s:
+ template:
+ - default
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_one: pod-2
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+ ignore_errors: True
+
+ - name: Assert that pod creation failed using template due to wrong parameter
+ assert:
+ that:
+ - r is failed
+
+ - name: Create pod using template (path parameter)
+ kubernetes.core.k8s:
+ template:
+ path: "pod_one.j2"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_one: pod-3
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+
+ - name: Assert that pod creation succeeded using template
+ assert:
+ that:
+ - r is successful
+
+ - name: Create pod using template (different variable string)
+ kubernetes.core.k8s:
+ template:
+ path: "pod_two.j2"
+ variable_start_string: '[['
+ variable_end_string: ']]'
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_two: pod-4
+ k8s_pod_namespace: "[[ template_namespace ]]"
+ ansible_python_interpreter: "[[ ansible_playbook_python ]]"
+ register: r
+
+ - name: Assert that pod creation succeeded using template
+ assert:
+ that:
+ - r is successful
+
+ - name: Create pods using multi-resource template
+ kubernetes.core.k8s:
+ template:
+ path: "pod_three.j2"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_three_one: pod-5
+ k8s_pod_name_three_two: pod-6
+ k8s_pod_namespace: "{{ template_namespace }}"
+ register: r
+
+ - name: Assert that pod creation succeeded using template
+ assert:
+ that:
+ - r is successful
+
+ - name: Create pods using list of template
+ kubernetes.core.k8s:
+ template:
+ - pod_one.j2
+ - path: "pod_two.j2"
+ variable_start_string: '[['
+ variable_end_string: ']]'
+ - path: "pod_three.j2"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name_one: pod-7
+ k8s_pod_name_two: pod-8
+ k8s_pod_name_three_one: pod-9
+ k8s_pod_name_three_two: pod-10
+ k8s_pod_namespace: "template-test"
+ register: r
+
+ - name: Assert that pod creation succeeded using template
+ assert:
+ that:
+ - r is successful
+
+ # continue_on_error
+ - name: define variable for test
+ set_fact:
+ k8s_pod_name_one: pod-11
+ k8s_pod_bad_name: pod-12
+ k8s_pod_namespace: "{{ template_namespace }}"
+ k8s_pod_bad_namespace: "dummy-namespace-012345"
+
+ - name: delete pod if it exists
+ kubernetes.core.k8s:
+ template: pod_one.j2
+ wait: true
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ state: absent
+
+ - name: create pod on bad namespace ( continue_on_error set to default(false) )
+ kubernetes.core.k8s:
+ template:
+ - pod_with_bad_namespace.j2
+ - pod_one.j2
+ register: resource
+ ignore_errors: true
+
+ - name: validate that creation failed
+ assert:
+ that:
+ - resource is failed
+ - '"Failed to create object" in resource.msg'
+
+ - name: assert pod has not been created
+ kubernetes.core.k8s_info:
+ kind: "{{ item.kind }}"
+ namespace: "{{ item.namespace }}"
+ name: "{{ item.name }}"
+ with_items:
+ - kind: pod
+ namespace: "{{ k8s_pod_bad_namespace }}"
+ name: "{{ k8s_pod_bad_name }}"
+ - kind: pod
+ namespace: "{{ k8s_pod_name_one }}"
+ name: "{{ k8s_pod_namespace }}"
+ register: resource
+
+ - name: check that resources creation failed
+ assert:
+ that:
+ - '{{ resource.results[0].resources | length == 0 }}'
+ - '{{ resource.results[1].resources | length == 0 }}'
+
+ - name: create pod without namespace (continue_on_error = true)
+ kubernetes.core.k8s:
+ template:
+ - pod_with_bad_namespace.j2
+ - pod_one.j2
+ continue_on_error: true
+ wait: true
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ register: resource
+ ignore_errors: true
+
+ - name: validate that creation succeeded
+ assert:
+ that:
+ - resource is successful
+
+ - name: validate resource creation succeeded for some and failed for others
+ assert:
+ that:
+ - resource is successful
+ - resource.result.results | selectattr('changed') | list | length == 1
+ - resource.result.results | selectattr('error', 'defined') | list | length == 1
+
+ # Test resource definition using template with 'omit'
+ - name: Deploy configmap using template
+ k8s:
+ namespace: "{{ template_namespace }}"
+ name: test-data
+ template: configmap.yml.j2
+
+ - name: Read configmap created
+ k8s_info:
+ kind: configmap
+ namespace: "{{ template_namespace }}"
+ name: test-data
+ register: _configmap
+
+ - name: Validate that the configmap does not contains annotations
+ assert:
+ that:
+ - '"annotations" not in _configmap.resources.0.metadata'
+
+ - name: Create resource once again
+ k8s:
+ namespace: "{{ template_namespace }}"
+ name: test-data
+ template: configmap.yml.j2
+ register: _configmap
+
+ - name: assert that nothing changed
+ assert:
+ that:
+ - _configmap is not changed
+
+ - name: Create resource once again (using description)
+ k8s:
+ namespace: "{{ template_namespace }}"
+ name: test-data
+ template: configmap.yml.j2
+ register: _configmap
+ vars:
+ k8s_configmap_desc: "This is a simple configmap used to test ansible k8s collection"
+
+ - name: assert that configmap was changed
+ assert:
+ that:
+ - _configmap is changed
+
+ - name: Read configmap created
+ k8s_info:
+ kind: configmap
+ namespace: "{{ template_namespace }}"
+ name: test-data
+ register: _configmap
+
+ - name: Validate that the configmap does not contains annotations
+ assert:
+ that:
+ - _configmap.resources.0.metadata.annotations.description == "This is a simple configmap used to test ansible k8s collection"
+
+ always:
+ - name: Remove namespace (Cleanup)
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ template_namespace }}"
+ state: absent
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/configmap.yml.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/configmap.yml.j2
new file mode 100644
index 00000000..bdca2e0b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/configmap.yml.j2
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ annotations:
+ description: "{{ k8s_configmap_desc | default(omit) }}"
+data:
+ key: "testing-template" \ No newline at end of file
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_one.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_one.j2
new file mode 100644
index 00000000..66970977
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_one.j2
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: "{{ k8s_pod_name_one }}"
+ name: '{{ k8s_pod_name_one }}'
+ namespace: '{{ k8s_pod_namespace }}'
+spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: '{{ k8s_pod_name_one }}'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_three.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_three.j2
new file mode 100644
index 00000000..6d6592f4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_three.j2
@@ -0,0 +1,35 @@
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: "{{ k8s_pod_name_three_one }}"
+ name: '{{ k8s_pod_name_three_one }}'
+ namespace: '{{ k8s_pod_namespace }}'
+spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: '{{ k8s_pod_name_three_one }}'
+
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: "{{ k8s_pod_name_three_two }}"
+ name: '{{ k8s_pod_name_three_two }}'
+ namespace: '{{ k8s_pod_namespace }}'
+spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: '{{ k8s_pod_name_three_two }}'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_two.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_two.j2
new file mode 100644
index 00000000..76820d49
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_two.j2
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: '[[ k8s_pod_name_two ]]'
+ name: '[[ k8s_pod_name_two ]]'
+ namespace: '[[ k8s_pod_namespace ]]'
+spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: '[[ k8s_pod_name_two ]]'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_with_bad_namespace.j2 b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_with_bad_namespace.j2
new file mode 100644
index 00000000..ce0ea80a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_template/templates/pod_with_bad_namespace.j2
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: "{{ k8s_pod_bad_name }}"
+ name: '{{ k8s_pod_bad_name }}'
+ namespace: '{{ k8s_pod_bad_namespace }}'
+spec:
+ containers:
+ - args:
+ - /bin/sh
+ - -c
+ - while true; do echo $(date); sleep 10; done
+ image: python:3.7-alpine
+ imagePullPolicy: Always
+ name: '{{ k8s_pod_bad_name }}'
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/aliases
new file mode 100644
index 00000000..c108b003
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/aliases
@@ -0,0 +1,2 @@
+k8s_cluster_info
+time=73
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/defaults/main.yml
new file mode 100644
index 00000000..e25ef1e9
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "user-impersonation"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/tasks/main.yml
new file mode 100644
index 00000000..fb95d597
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_user_impersonation/tasks/main.yml
@@ -0,0 +1,220 @@
+- block:
+ - set_fact:
+ test_ns: "{{ test_namespace }}"
+ pod_name: "impersonate-pod"
+ # this use will have authorization to list/create pods in the namespace
+ user_01: "authorized-sa-01"
+ # No authorization attached to this user, will use 'user_01' for impersonation
+ user_02: "unauthorize-sa-01"
+
+ - name: Get cluster information
+ kubernetes.core.k8s_cluster_info:
+ register: cluster_info
+ no_log: true
+
+ - set_fact:
+ cluster_host: "{{ cluster_info['connection']['host'] }}"
+
+ - name: Create Service account
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: "{{ item }}"
+ namespace: "{{ test_ns }}"
+ with_items:
+ - "{{ user_01 }}"
+ - "{{ user_02 }}"
+
+ - name: Create Service token
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: Secret
+ type: kubernetes.io/service-account-token
+ metadata:
+ name: "{{ item }}"
+ annotations:
+ kubernetes.io/service-account.name: "{{ item }}"
+ namespace: "{{ test_ns }}"
+ with_items:
+ - "{{ user_01 }}"
+ - "{{ user_02 }}"
+
+ - name: Read Service Account - user_01
+ kubernetes.core.k8s_info:
+ kind: ServiceAccount
+ namespace: "{{ test_ns }}"
+ name: "{{ user_01 }}"
+ register: result
+
+ - name: Get secret details
+ kubernetes.core.k8s_info:
+ kind: Secret
+ namespace: '{{ test_ns }}'
+ name: '{{ user_01 }}'
+ no_log: true
+ register: _secret
+
+ - set_fact:
+ user_01_api_token: "{{ _secret.resources[0]['data']['token'] | b64decode }}"
+
+ - name: Read Service Account - user_02
+ kubernetes.core.k8s_info:
+ kind: Secret
+ namespace: "{{ test_ns }}"
+ name: "{{ user_02 }}"
+ register: result
+
+ - name: Get secret details
+ kubernetes.core.k8s_info:
+ kind: Secret
+ namespace: '{{ test_ns }}'
+ name: '{{ user_02 }}'
+ no_log: true
+ register: _secret
+
+ - set_fact:
+ user_02_api_token: "{{ _secret.resources[0]['data']['token'] | b64decode }}"
+
+ - name: Create Role to manage pod on the namespace
+ kubernetes.core.k8s:
+ namespace: "{{ test_ns }}"
+ definition:
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ name: pod-manager
+ rules:
+ - apiGroups: [""]
+ resources: ["pods"]
+ verbs: ["create", "get", "delete", "list", "patch"]
+
+ - name: Attach Role to the user_01
+ kubernetes.core.k8s:
+ namespace: "{{ test_ns }}"
+ definition:
+ kind: RoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+ name: pod-manager-binding
+ subjects:
+ - kind: ServiceAccount
+ name: "{{ user_01 }}"
+ roleRef:
+ kind: Role
+ name: pod-manager
+ apiGroup: rbac.authorization.k8s.io
+
+ - name: Create Pod using user_01 credentials
+ kubernetes.core.k8s:
+ api_key: "{{ user_01_api_token }}"
+ host: "{{ cluster_host }}"
+ validate_certs: no
+ namespace: "{{ test_ns }}"
+ name: "{{ pod_name }}"
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ labels:
+ test: "impersonate"
+ spec:
+ containers:
+ - name: c0
+ image: busybox
+ command:
+ - /bin/sh
+ - -c
+ - while true;do date;sleep 5; done
+
+ - name: Delete Pod using user_02 credentials should failed
+ kubernetes.core.k8s:
+ api_key: "{{ user_02_api_token }}"
+ host: "{{ cluster_host }}"
+ validate_certs: no
+ namespace: "{{ test_ns }}"
+ name: "{{ pod_name }}"
+ kind: Pod
+ state: absent
+ register: delete_pod
+ ignore_errors: true
+
+ - name: Assert that operation has failed
+ assert:
+ that:
+ - delete_pod is failed
+ - delete_pod.reason == 'Forbidden'
+
+ - name: Delete Pod using user_02 credentials and impersonation to user_01
+ kubernetes.core.k8s:
+ api_key: "{{ user_02_api_token }}"
+ host: "{{ cluster_host }}"
+ validate_certs: no
+ impersonate_user: "system:serviceaccount:{{ test_ns }}:{{ user_01 }}"
+ namespace: "{{ test_ns }}"
+ name: "{{ pod_name }}"
+ kind: Pod
+ state: absent
+ ignore_errors: true
+ register: delete_pod_2
+
+ - name: Assert that operation has failed
+ assert:
+ that:
+ - delete_pod_2 is failed
+ - delete_pod_2.reason == 'Forbidden'
+ - '"cannot impersonate resource" in delete_pod_2.msg'
+
+ - name: Create Role to impersonate user_01
+ kubernetes.core.k8s:
+ namespace: "{{ test_ns }}"
+ definition:
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ name: sa-impersonate
+ rules:
+ - apiGroups: [""]
+ resources:
+ - serviceaccounts
+ verbs:
+ - impersonate
+ resourceNames:
+ - "{{ user_01 }}"
+
+ - name: Attach Role to the user_02
+ kubernetes.core.k8s:
+ namespace: "{{ test_ns }}"
+ definition:
+ kind: RoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+ name: sa-impersonate-binding
+ subjects:
+ - kind: ServiceAccount
+ name: "{{ user_02 }}"
+ roleRef:
+ kind: Role
+ name: sa-impersonate
+ apiGroup: rbac.authorization.k8s.io
+
+ - name: Delete Pod using user_02 credentials should succeed now
+ kubernetes.core.k8s:
+ api_key: "{{ user_02_api_token }}"
+ host: "{{ cluster_host }}"
+ validate_certs: no
+ impersonate_user: "system:serviceaccount:{{ test_ns }}:{{ user_01 }}"
+ namespace: "{{ test_ns }}"
+ name: "{{ pod_name }}"
+ kind: Pod
+ state: absent
+
+ always:
+ - name: Ensure namespace is deleted
+ kubernetes.core.k8s:
+ state: absent
+ kind: Namespace
+ name: "{{ test_ns }}"
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/aliases
new file mode 100644
index 00000000..64f66d0a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/aliases
@@ -0,0 +1,3 @@
+time=59
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/defaults/main.yml
new file mode 100644
index 00000000..f868ad2d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "validate"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/tasks/main.yml
new file mode 100644
index 00000000..900e6f70
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_validate/tasks/main.yml
@@ -0,0 +1,232 @@
+- block:
+ - 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
+ - "'Failed to import the required Python library (kubernetes-validate)' in k8s_no_validate.msg"
+
+ - 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:
+ - '"This is required to use in-memory config." 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: "{{ test_namespace }}"
+ environment:
+ ENABLE_TURBO_MODE: false
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/aliases
new file mode 100644
index 00000000..295a154c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/aliases
@@ -0,0 +1,5 @@
+# duration 10min
+slow
+time=504
+k8s
+k8s_info
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/defaults/main.yml
new file mode 100644
index 00000000..04c873ce
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/defaults/main.yml
@@ -0,0 +1,40 @@
+---
+k8s_pod_metadata:
+ labels:
+ app: "{{ k8s_pod_name }}"
+
+k8s_pod_spec:
+ serviceAccount: "{{ k8s_pod_service_account }}"
+ containers:
+ - image: "{{ k8s_pod_image }}"
+ imagePullPolicy: Always
+ name: "{{ k8s_pod_name }}"
+ command: "{{ k8s_pod_command }}"
+ readinessProbe:
+ initialDelaySeconds: 15
+ exec:
+ command:
+ - /bin/true
+ resources: "{{ k8s_pod_resources }}"
+ ports: "{{ k8s_pod_ports }}"
+ env: "{{ k8s_pod_env }}"
+
+
+k8s_pod_service_account: default
+
+k8s_pod_resources:
+ limits:
+ cpu: "100m"
+ memory: "100Mi"
+
+k8s_pod_command: []
+
+k8s_pod_ports: []
+
+k8s_pod_env: []
+
+k8s_pod_template:
+ metadata: "{{ k8s_pod_metadata }}"
+ spec: "{{ k8s_pod_spec }}"
+
+test_namespace: "wait"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/tasks/main.yml
new file mode 100644
index 00000000..1cb7e4c4
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/k8s_waiter/tasks/main.yml
@@ -0,0 +1,470 @@
+---
+- block:
+ - set_fact:
+ wait_namespace: "{{ test_namespace }}"
+ k8s_wait_timeout: 400
+
+ - name: Add a simple pod
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ k8s_pod_name }}"
+ namespace: "{{ wait_namespace }}"
+ spec: "{{ k8s_pod_spec }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-pod
+ k8s_pod_image: alpine:3.8
+ k8s_pod_command:
+ - sleep
+ - "10000"
+
+ - name: Add a daemonset
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: wait-daemonset
+ namespace: "{{ wait_namespace }}"
+ spec:
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_sleep: 5
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-ds
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1
+ k8s_pod_command:
+ - sleep
+ - "600"
+ register: ds
+
+ - name: Check that daemonset wait worked
+ assert:
+ that:
+ - ds.result.status.currentNumberScheduled == ds.result.status.desiredNumberScheduled
+
+ - name: Update a daemonset in check_mode
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: wait-daemonset
+ 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-ds
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2
+ k8s_pod_command:
+ - sleep
+ - "600"
+ register: update_ds_check_mode
+ check_mode: yes
+
+ - name: Check that check_mode result contains the changes
+ assert:
+ that:
+ - update_ds_check_mode is changed
+ - "update_ds_check_mode.result.spec.template.spec.containers[0].image == 'gcr.io/kuar-demo/kuard-amd64:2'"
+
+ - name: Update a daemonset
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+ name: wait-daemonset
+ namespace: "{{ wait_namespace }}"
+ spec:
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ updateStrategy:
+ type: RollingUpdate
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_sleep: 3
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-ds
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:3
+ k8s_pod_command:
+ - sleep
+ - "600"
+ register: ds
+
+ - name: Get updated pods
+ k8s_info:
+ api_version: v1
+ kind: Pod
+ namespace: "{{ wait_namespace }}"
+ label_selectors:
+ - app=wait-ds
+ field_selectors:
+ - status.phase=Running
+ register: updated_ds_pods
+
+ - name: Check that daemonset wait worked
+ assert:
+ that:
+ - 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: "{{ k8s_wait_timeout | default(omit) }}"
+ 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: "{{ k8s_wait_timeout | default(omit) }}"
+ 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:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ k8s_pod_name }}"
+ namespace: "{{ wait_namespace }}"
+ spec: "{{ k8s_pod_spec }}"
+ wait: yes
+ wait_sleep: 1
+ wait_timeout: 30
+ vars:
+ k8s_pod_name: wait-crash-pod
+ k8s_pod_image: alpine:3.8
+ k8s_pod_command:
+ - /bin/false
+ register: crash_pod
+ ignore_errors: yes
+
+ - name: Check that task failed
+ assert:
+ that:
+ - crash_pod is failed
+
+ - name: Use a non-existent image
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: "{{ k8s_pod_name }}"
+ namespace: "{{ wait_namespace }}"
+ spec: "{{ k8s_pod_spec }}"
+ wait: yes
+ wait_sleep: 1
+ wait_timeout: 30
+ vars:
+ k8s_pod_name: wait-no-image-pod
+ k8s_pod_image: i_made_this_up:and_this_too
+ register: no_image_pod
+ ignore_errors: yes
+
+ - name: Check that task failed
+ assert:
+ that:
+ - no_image_pod is failed
+
+ - name: Add a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: wait-deploy
+ namespace: "{{ wait_namespace }}"
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:1
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+
+ register: deploy
+
+ - name: Check that deployment wait worked
+ assert:
+ that:
+ - deploy.result.status.availableReplicas == deploy.result.status.replicas
+
+ - name: Update a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: wait-deploy
+ namespace: "{{ wait_namespace }}"
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-deploy
+ k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:2
+ k8s_pod_ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ register: update_deploy
+
+ # It looks like the Deployment is updated to have the desired state *before* the pods are terminated
+ # Wait a couple of seconds to allow the old pods to at least get to Terminating state
+ - name: Avoid race condition
+ pause:
+ seconds: 2
+
+ - name: Get updated pods
+ k8s_info:
+ api_version: v1
+ kind: Pod
+ namespace: "{{ wait_namespace }}"
+ label_selectors:
+ - app=wait-deploy
+ field_selectors:
+ - status.phase=Running
+ register: updated_deploy_pods
+ until: updated_deploy_pods.resources[0].spec.containers[0].image.endswith(':2')
+ retries: 6
+ delay: 5
+
+ - name: Check that deployment wait worked
+ assert:
+ that:
+ - deploy.result.status.availableReplicas == deploy.result.status.replicas
+
+ - name: Pause a deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: wait-deploy
+ namespace: "{{ wait_namespace }}"
+ spec:
+ paused: True
+ apply: no
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ wait_condition:
+ type: Progressing
+ status: Unknown
+ reason: DeploymentPaused
+ register: pause_deploy
+
+ - name: Check that paused deployment wait worked
+ assert:
+ that:
+ - condition.reason == "DeploymentPaused"
+ - condition.status == "Unknown"
+ vars:
+ condition: '{{ pause_deploy.result.status.conditions[1] }}'
+
+ - name: Add a service based on the deployment
+ k8s:
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: wait-svc
+ namespace: "{{ wait_namespace }}"
+ spec:
+ selector:
+ app: "{{ k8s_pod_name }}"
+ ports:
+ - port: 8080
+ targetPort: 8080
+ protocol: TCP
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-deploy
+ register: service
+
+ - name: Assert that waiting for service works
+ assert:
+ that:
+ - service is successful
+
+ - name: Add a crashing deployment
+ k8s:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: wait-crash-deploy
+ namespace: "{{ wait_namespace }}"
+ spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: "{{ k8s_pod_name }}"
+ template: "{{ k8s_pod_template }}"
+ wait: yes
+ wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
+ vars:
+ k8s_pod_name: wait-crash-deploy
+ k8s_pod_image: alpine:3.8
+ k8s_pod_command:
+ - /bin/false
+ register: wait_crash_deploy
+ ignore_errors: yes
+
+ - name: Check that task failed
+ assert:
+ that:
+ - wait_crash_deploy is failed
+
+ - name: Remove Pod with very short timeout
+ k8s:
+ api_version: v1
+ kind: Pod
+ name: wait-pod
+ namespace: "{{ wait_namespace }}"
+ state: absent
+ wait: yes
+ wait_sleep: 2
+ wait_timeout: 5
+ ignore_errors: yes
+ register: short_wait_remove_pod
+
+ - name: Check that task failed
+ assert:
+ that:
+ - short_wait_remove_pod is failed
+
+ always:
+ - name: Remove namespace
+ k8s:
+ kind: Namespace
+ name: "{{ wait_namespace }}"
+ state: absent
+ ignore_errors: yes
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/aliases
new file mode 100644
index 00000000..db0ee6ed
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/aliases
@@ -0,0 +1,3 @@
+context/target
+time=16
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/defaults/main.yml
new file mode 100644
index 00000000..96128d9b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+test_namespace:
+ - app-development-one
+ - app-development-two
+ - app-development-three
+configmap_data: "This is a simple config map data."
+configmap_name: "test-configmap"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/meta/main.yml
new file mode 100644
index 00000000..54561c97
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- remove_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/tasks/main.yml
new file mode 100644
index 00000000..63723b1d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_k8s/tasks/main.yml
@@ -0,0 +1,240 @@
+---
+- block:
+ - 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=test_namespace[0]) }}"
+ pre_test3: "{{ query('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
+ pre_test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[0]) }}"
+ cluster_version: "{{ query('kubernetes.core.k8s', cluster_info='version') }}"
+ cluster_api_groups: "{{ query('kubernetes.core.k8s', cluster_info='api_groups') }}"
+
+ # https://github.com/ansible-collections/kubernetes.core/issues/147
+ - name: Create a namespace with label
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ test_namespace[0] }}"
+ labels:
+ namespace_label: "app_development"
+
+ - set_fact:
+ 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=test_namespace[0], wantlist=True) }}"
+ test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[0]) }}"
+ test5: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
+ test6: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[0]) }}"
+ test7: "{{ lookup('kubernetes.core.k8s', kind='Ingress', api_version='networking.k8s.io/vINVALID', errors='ignore') }}"
+
+ - 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:
+ # 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:
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ test_namespace[1] }}"
+ labels:
+ namespace_label: "app_development"
+
+ - set_fact:
+ 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=test_namespace[0], wantlist=True) }}"
+ test4: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[0]) }}"
+ test5: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[1], wantlist=True) }}"
+ test6: "{{ query('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[1]) }}"
+ test7: "{{ lookup('kubernetes.core.k8s', kind='Namespace', label_selector='namespace_label=app_development') }}"
+ test8: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[0]) }}"
+ test9: "{{ lookup('kubernetes.core.k8s', kind='Namespace', resource_name=test_namespace[1]) }}"
+
+ - name: Assert that every test is passed after creating second object
+ assert:
+ that:
+ # 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
+
+ # test using resource_definition
+ - k8s:
+ name: "{{ test_namespace[2] }}"
+ kind: Namespace
+
+ - set_fact:
+ configmap_def:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: "{{ configmap_name }}"
+ namespace: "{{ test_namespace[2] }}"
+ data:
+ value: "{{ configmap_data }}"
+
+ - name: Create simple configmap
+ k8s:
+ definition: "{{ configmap_def }}"
+
+ - name: Retrieve configmap using resource_definition parameter
+ set_fact:
+ result_configmap: "{{ lookup('kubernetes.core.k8s', resource_definition=configmap_def) }}"
+
+ - name: Validate configmap result
+ assert:
+ that:
+ - result_configmap.apiVersion == 'v1'
+ - result_configmap.metadata.name == "{{ configmap_name }}"
+ - result_configmap.metadata.namespace == "{{ test_namespace[2] }}"
+ - result_configmap.data.value == "{{ configmap_data }}"
+
+ # test lookup plugin using src parameter
+ - block:
+ - name: Create temporary file to store content
+ tempfile:
+ suffix: ".yaml"
+ register: tmpfile
+
+ - name: Copy content into file
+ copy:
+ content: |
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: "{{ configmap_name }}"
+ namespace: "{{ test_namespace[2] }}"
+ dest: "{{ tmpfile.path }}"
+
+ - name: Retrieve configmap using src parameter
+ set_fact:
+ src_configmap: "{{ lookup('kubernetes.core.k8s', src=tmpfile.path) }}"
+
+ - name: Validate configmap result
+ assert:
+ that:
+ - src_configmap.apiVersion == 'v1'
+ - src_configmap.metadata.name == "{{ configmap_name }}"
+ - src_configmap.metadata.namespace == "{{ test_namespace[2] }}"
+ - src_configmap.data.value == "{{ configmap_data }}"
+
+ always:
+ - name: Delete temporary file created
+ file:
+ state: absent
+ path: "{{ tmpfile.path }}"
+ ignore_errors: true
+
+ # test using aliases for user authentication
+ - block:
+ - name: Create temporary directory to save user credentials
+ tempfile:
+ state: directory
+ suffix: ".config"
+ register: tmpdir
+
+ - include_role:
+ name: setup_kubeconfig
+ vars:
+ user_credentials_dir: "{{ tmpdir.path }}"
+ kubeconfig_operation: "save"
+
+ - set_fact:
+ cluster_host: "{{ lookup('file', tmpdir.path + '/host_data.txt') }}"
+ user_cert_file: "{{ tmpdir.path }}/cert_file_data.txt"
+ user_key_file: "{{ tmpdir.path }}/key_file_data.txt"
+ ssl_ca_cert: "{{ tmpdir.path }}/ssl_ca_cert_data.txt"
+
+ - name: Retrieve configmap using authentication aliases (validate_certs=false)
+ set_fact:
+ configmap_no_ssl: "{{ lookup('kubernetes.core.k8s', host=cluster_host, cert_file=user_cert_file, key_file=user_key_file, verify_ssl=false, resource_definition=configmap_def) }}"
+
+ - name: Validate configmap result
+ assert:
+ that:
+ - configmap_no_ssl.apiVersion == 'v1'
+ - configmap_no_ssl.metadata.name == "{{ configmap_name }}"
+ - configmap_no_ssl.metadata.namespace == "{{ test_namespace[2] }}"
+ - configmap_no_ssl.data.value == "{{ configmap_data }}"
+
+ - name: Retrieve configmap using authentication aliases (validate_certs=true)
+ set_fact:
+ configmap_with_ssl: "{{ lookup('kubernetes.core.k8s', host=cluster_host, cert_file=user_cert_file, key_file=user_key_file, ssl_ca_cert=ssl_ca_cert, verify_ssl=true, resource_definition=configmap_def) }}"
+
+ - name: Validate configmap result
+ assert:
+ that:
+ - configmap_with_ssl.apiVersion == 'v1'
+ - configmap_with_ssl.metadata.name == "{{ configmap_name }}"
+ - configmap_with_ssl.metadata.namespace == "{{ test_namespace[2] }}"
+ - configmap_with_ssl.data.value == "{{ configmap_data }}"
+
+ always:
+ - name: Delete temporary directory
+ file:
+ state: absent
+ path: "{{ tmpdir.path }}"
+ ignore_errors: true
+
+ - include_role:
+ name: setup_kubeconfig
+ ignore_errors: true
+ vars:
+ kubeconfig_operation: revert
+
+ always:
+ - name: Ensure that namespace is removed
+ k8s:
+ kind: Namespace
+ name: "app-development-{{ item }}"
+ state: absent
+ with_items:
+ - one
+ - two
+ - three
+ ignore_errors: true
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/aliases
new file mode 100644
index 00000000..6e6cbcf1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/aliases
@@ -0,0 +1,3 @@
+context/target
+time=44
+k8s
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/defaults/main.yml
new file mode 100644
index 00000000..bc7e6fbe
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+test_namespace: "kustomize"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/meta/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/meta/main.yml
new file mode 100644
index 00000000..08362c78
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+- setup_namespace
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/tasks/main.yml
new file mode 100644
index 00000000..dafdcdd0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/lookup_kustomize/tasks/main.yml
@@ -0,0 +1,108 @@
+---
+- block:
+ - set_fact:
+ kustomize_ns: "{{ test_namespace }}"
+
+ - name: create environment for test
+ block:
+
+ - 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_release: "v1.22.0"
+ kubectl_binary: "{{ tmp_dir_path }}/kubectl"
+
+ - name: Install Kubectl
+ ansible.builtin.get_url:
+ url: "https://dl.k8s.io/release/{{ kubectl_release }}/bin/linux/amd64/kubectl"
+ dest: "{{ kubectl_binary }}"
+ register: result
+ until: result is not failed
+ retries: 3
+ delay: 60
+ become: true
+
+ - name: Make kubectl as executable
+ ansible.builtin.file:
+ path: '{{ item }}'
+ mode: '0755'
+ become: true
+ with_items:
+ - "{{ kubectl_binary }}"
+
+ - 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 }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/aliases
new file mode 100644
index 00000000..7a68b11d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/aliases
@@ -0,0 +1 @@
+disabled
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/tasks/main.yml
new file mode 100644
index 00000000..b715c383
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/remove_namespace/tasks/main.yml
@@ -0,0 +1,17 @@
+---
+- name: Delete existing namespace
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ item }}"
+ state: absent
+ wait: yes
+ with_items: "{{ test_namespace }}"
+ when: test_namespace | type_debug == "list"
+
+- name: Delete existing namespace
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ test_namespace }}"
+ state: absent
+ wait: yes
+ when: test_namespace | type_debug == "AnsibleUnicode"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/aliases
new file mode 100644
index 00000000..7a68b11d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/aliases
@@ -0,0 +1 @@
+disabled
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/defaults/main.yml
new file mode 100644
index 00000000..35a2f093
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+# When set to 'revert', the role will copy saved kubeconfig to the default location
+# When set to 'save', the role will copy default kubeconfig to the custom location
+kubeconfig_operation: "revert"
+kubeconfig_default_path: "~/.kube/config"
+kubeconfig_custom_path: "~/.kube/customconfig"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py
new file mode 100644
index 00000000..9c9e8796
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py
@@ -0,0 +1,126 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2022, 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: test_inventory_read_credentials
+
+short_description: Generate cert_file, key_file, host and server certificate
+
+author:
+ - Aubin Bikouo (@abikouo)
+
+description:
+ - This module is used for integration testing only for this collection
+ - The module load a kube_config file and generate parameters used to authenticate the client.
+
+options:
+ kube_config:
+ description:
+ - Path to a valid kube config file to test.
+ type: path
+ required: yes
+ dest_dir:
+ description:
+ - Path to a directory where file will be generated.
+ type: path
+ required: yes
+"""
+
+EXAMPLES = r"""
+- name: Generate authentication parameters for current context
+ test_inventory_read_credentials:
+ kube_config: ~/.kube/config
+ dest_dir: /tmp
+"""
+
+
+RETURN = """
+auth:
+ description:
+ - User information used to authenticate to the cluster.
+ returned: always
+ type: complex
+ contains:
+ cert_file:
+ description:
+ - Path to the generated user certificate file.
+ type: str
+ key_file:
+ description:
+ - Path to the generated user key file.
+ type: str
+ ssl_ca_cert:
+ description:
+ - Path to the generated server certificate file.
+ type: str
+ host:
+ description:
+ - Path to the file containing cluster host.
+ type: str
+"""
+
+import os
+import shutil
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ get_api_client,
+)
+
+
+class K8SInventoryTestModule(AnsibleModule):
+ def __init__(self):
+
+ argument_spec = dict(
+ kube_config=dict(required=True, type="path"),
+ dest_dir=dict(required=True, type="path"),
+ )
+
+ super(K8SInventoryTestModule, self).__init__(argument_spec=argument_spec)
+ self.execute_module()
+
+ def execute_module(self):
+
+ dest_dir = os.path.abspath(self.params.get("dest_dir"))
+ kubeconfig_path = self.params.get("kube_config")
+ if not os.path.isdir(dest_dir):
+ self.fail_json(
+ msg="The following {0} does not exist or is not a directory.".format(
+ dest_dir
+ )
+ )
+ if not os.path.isfile(kubeconfig_path):
+ self.fail_json(
+ msg="The following {0} does not exist or is not a valid file.".format(
+ kubeconfig_path
+ )
+ )
+
+ client = get_api_client(kubeconfig=kubeconfig_path)
+
+ result = dict(host=os.path.join(dest_dir, "host_data.txt"))
+ # create file containing host information
+ with open(result["host"], "w") as fd:
+ fd.write(client.configuration.host)
+ for key in ("cert_file", "key_file", "ssl_ca_cert"):
+ dest_file = os.path.join(dest_dir, "{0}_data.txt".format(key))
+ shutil.copyfile(getattr(client.configuration, key), dest_file)
+ result[key] = dest_file
+
+ self.exit_json(auth=result)
+
+
+def main():
+ K8SInventoryTestModule()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/tasks/main.yml
new file mode 100644
index 00000000..fcde7a72
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_kubeconfig/tasks/main.yml
@@ -0,0 +1,46 @@
+---
+- fail:
+ msg: "kubeconfig_operation must be one of 'revert' or 'save'"
+ when: kubeconfig_operation not in ["revert", "save"]
+
+- set_fact:
+ src_kubeconfig: "{{ (kubeconfig_operation == 'save') | ternary(kubeconfig_default_path, kubeconfig_custom_path) }}"
+ dest_kubeconfig: "{{ (kubeconfig_operation == 'save') | ternary(kubeconfig_custom_path, kubeconfig_default_path) }}"
+
+- name: check if source kubeconfig exists
+ stat:
+ path: "{{ src_kubeconfig }}"
+ register: _src
+
+- name: check if destination kubeconfig exists
+ stat:
+ path: "{{ dest_kubeconfig }}"
+ register: _dest
+
+- fail:
+ msg: "Both {{ src_kubeconfig }} and {{ dest_kubeconfig }} do not exist."
+ when:
+ - not _src.stat.exists
+ - not _dest.stat.exists
+
+- name: Generate user cert_file, key_file, and hostname
+ block:
+ - name: Generate user credentials files
+ test_inventory_read_credentials:
+ kube_config: "{{ (_src.stat.exists) | ternary(src_kubeconfig, dest_kubeconfig) }}"
+ dest_dir: "{{ user_credentials_dir }}"
+ when: user_credentials_dir is defined
+
+- block:
+ - name: "Copy {{ src_kubeconfig }} into {{ dest_kubeconfig }}"
+ copy:
+ remote_src: true
+ src: "{{ src_kubeconfig }}"
+ dest: "{{ dest_kubeconfig }}"
+
+ - name: "Delete {{ src_kubeconfig }}"
+ file:
+ state: absent
+ path: "{{ src_kubeconfig }}"
+
+ when: _src.stat.exists
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/aliases b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/aliases
new file mode 100644
index 00000000..7a68b11d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/aliases
@@ -0,0 +1 @@
+disabled
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/defaults/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/defaults/main.yml
new file mode 100644
index 00000000..ed97d539
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/defaults/main.yml
@@ -0,0 +1 @@
+---
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/create.yml b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/create.yml
new file mode 100644
index 00000000..a25780ec
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/create.yml
@@ -0,0 +1,22 @@
+---
+- name: "Read namespace {{ namespace_to_create }}"
+ kubernetes.core.k8s_info:
+ kind: Namespace
+ name: "{{ namespace_to_create }}"
+ register: _namespace
+
+- name: Delete existing namespace
+ kubernetes.core.k8s:
+ kind: Namespace
+ name: "{{ namespace_to_create }}"
+ state: absent
+ wait: yes
+
+- name: "Ensure namespace {{ namespace_to_create }}"
+ kubernetes.core.k8s:
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: "{{ namespace_to_create }}"
+ labels: "{{ namespace_labels | default(omit) }}"
diff --git a/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/main.yml b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/main.yml
new file mode 100644
index 00000000..032444e1
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/integration/targets/setup_namespace/tasks/main.yml
@@ -0,0 +1,13 @@
+---
+- include_tasks: tasks/create.yml
+ vars:
+ namespace_to_create: "{{ item.name | default(item) }}"
+ namespace_labels: "{{ item.labels | default(omit) }}"
+ with_items: "{{ test_namespace }}"
+ when: test_namespace | type_debug == "list"
+
+- include_tasks: tasks/create.yml
+ vars:
+ namespace_to_create: "{{ test_namespace }}"
+ namespace_labels: "{{ test_namespace_labels | default(omit) }}"
+ when: test_namespace | type_debug == "AnsibleUnicode"
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.10.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.10.txt
new file mode 100644
index 00000000..b617363d
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.10.txt
@@ -0,0 +1,616 @@
+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/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/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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip
+plugins/doc_fragments/__init__.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip
+plugins/module_utils/helm.py future-import-boilerplate!skip
+plugins/module_utils/apply.py future-import-boilerplate!skip
+plugins/module_utils/hashes.py future-import-boilerplate!skip
+plugins/module_utils/helm_args_common.py future-import-boilerplate!skip
+plugins/module_utils/version.py future-import-boilerplate!skip
+plugins/module_utils/_version.py future-import-boilerplate!skip
+plugins/module_utils/copy.py future-import-boilerplate!skip
+plugins/module_utils/args_common.py future-import-boilerplate!skip
+plugins/module_utils/__init__.py future-import-boilerplate!skip
+plugins/module_utils/selector.py future-import-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
+plugins/module_utils/common.py future-import-boilerplate!skip
+plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
+plugins/module_utils/exceptions.py future-import-boilerplate!skip
+plugins/module_utils/client/resource.py future-import-boilerplate!skip
+plugins/module_utils/client/discovery.py future-import-boilerplate!skip
+plugins/module_utils/k8s/resource.py future-import-boilerplate!skip
+plugins/module_utils/k8s/core.py future-import-boilerplate!skip
+plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip
+plugins/module_utils/k8s/client.py future-import-boilerplate!skip
+plugins/module_utils/k8s/runner.py future-import-boilerplate!skip
+plugins/module_utils/k8s/service.py future-import-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip
+plugins/connection/kubectl.py future-import-boilerplate!skip
+plugins/inventory/k8s.py future-import-boilerplate!skip
+plugins/lookup/k8s.py future-import-boilerplate!skip
+plugins/lookup/kustomize.py future-import-boilerplate!skip
+plugins/modules/k8s_scale.py future-import-boilerplate!skip
+plugins/modules/helm_template.py future-import-boilerplate!skip
+plugins/modules/k8s_exec.py future-import-boilerplate!skip
+plugins/modules/helm.py future-import-boilerplate!skip
+plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
+plugins/modules/helm_info.py future-import-boilerplate!skip
+plugins/modules/helm_repository.py future-import-boilerplate!skip
+plugins/modules/k8s_rollback.py future-import-boilerplate!skip
+plugins/modules/k8s_log.py future-import-boilerplate!skip
+plugins/modules/k8s_drain.py future-import-boilerplate!skip
+plugins/modules/helm_plugin.py future-import-boilerplate!skip
+plugins/modules/k8s_taint.py future-import-boilerplate!skip
+plugins/modules/k8s.py future-import-boilerplate!skip
+plugins/modules/k8s_service.py future-import-boilerplate!skip
+plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
+plugins/modules/k8s_info.py future-import-boilerplate!skip
+plugins/modules/k8s_cp.py future-import-boilerplate!skip
+plugins/modules/__init__.py future-import-boilerplate!skip
+plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
+plugins/action/k8s_info.py future-import-boilerplate!skip
+plugins/filter/k8s.py future-import-boilerplate!skip
+tests/unit/conftest.py future-import-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip
+tests/unit/module_utils/test_helm.py future-import-boilerplate!skip
+tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
+tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip
+tests/unit/module_utils/test_resource.py future-import-boilerplate!skip
+tests/unit/module_utils/test_service.py future-import-boilerplate!skip
+tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip
+tests/unit/module_utils/test_common.py future-import-boilerplate!skip
+tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
+tests/unit/module_utils/test_apply.py future-import-boilerplate!skip
+tests/unit/module_utils/test_runner.py future-import-boilerplate!skip
+tests/unit/module_utils/test_client.py future-import-boilerplate!skip
+tests/unit/module_utils/test_core.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template.py future-import-boilerplate!skip
+tests/unit/modules/test_module_helm.py future-import-boilerplate!skip
+tests/unit/action/test_remove_omit.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/__init__.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip
+plugins/module_utils/helm.py metaclass-boilerplate!skip
+plugins/module_utils/apply.py metaclass-boilerplate!skip
+plugins/module_utils/hashes.py metaclass-boilerplate!skip
+plugins/module_utils/helm_args_common.py metaclass-boilerplate!skip
+plugins/module_utils/version.py metaclass-boilerplate!skip
+plugins/module_utils/_version.py metaclass-boilerplate!skip
+plugins/module_utils/copy.py metaclass-boilerplate!skip
+plugins/module_utils/args_common.py metaclass-boilerplate!skip
+plugins/module_utils/__init__.py metaclass-boilerplate!skip
+plugins/module_utils/selector.py metaclass-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
+plugins/module_utils/common.py metaclass-boilerplate!skip
+plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
+plugins/module_utils/exceptions.py metaclass-boilerplate!skip
+plugins/module_utils/client/resource.py metaclass-boilerplate!skip
+plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/core.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/client.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/service.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip
+plugins/connection/kubectl.py metaclass-boilerplate!skip
+plugins/inventory/k8s.py metaclass-boilerplate!skip
+plugins/lookup/k8s.py metaclass-boilerplate!skip
+plugins/lookup/kustomize.py metaclass-boilerplate!skip
+plugins/modules/k8s_scale.py metaclass-boilerplate!skip
+plugins/modules/helm_template.py metaclass-boilerplate!skip
+plugins/modules/k8s_exec.py metaclass-boilerplate!skip
+plugins/modules/helm.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
+plugins/modules/helm_info.py metaclass-boilerplate!skip
+plugins/modules/helm_repository.py metaclass-boilerplate!skip
+plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
+plugins/modules/k8s_log.py metaclass-boilerplate!skip
+plugins/modules/k8s_drain.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin.py metaclass-boilerplate!skip
+plugins/modules/k8s_taint.py metaclass-boilerplate!skip
+plugins/modules/k8s.py metaclass-boilerplate!skip
+plugins/modules/k8s_service.py metaclass-boilerplate!skip
+plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_cp.py metaclass-boilerplate!skip
+plugins/modules/__init__.py metaclass-boilerplate!skip
+plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
+plugins/action/k8s_info.py metaclass-boilerplate!skip
+plugins/filter/k8s.py metaclass-boilerplate!skip
+tests/unit/conftest.py metaclass-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_service.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_common.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_client.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_core.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip
+tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip
+tests/unit/action/test_remove_omit.py metaclass-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/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/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/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_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_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_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/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_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_drain.py import-2.6!skip
+plugins/modules/k8s_drain.py import-2.7!skip
+plugins/modules/k8s_drain.py import-3.5!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/k8s_taint.py import-2.6!skip
+plugins/modules/k8s_taint.py import-2.7!skip
+plugins/modules/k8s_taint.py import-3.5!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_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_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_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_cp.py import-2.6!skip
+plugins/modules/k8s_cp.py import-2.7!skip
+plugins/modules/k8s_cp.py import-3.5!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/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/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/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/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/helm_args_common.py import-2.6!skip
+plugins/module_utils/helm_args_common.py import-2.7!skip
+plugins/module_utils/helm_args_common.py import-3.5!skip
+plugins/module_utils/version.py import-2.6!skip
+plugins/module_utils/version.py import-2.7!skip
+plugins/module_utils/version.py import-3.5!skip
+plugins/module_utils/_version.py import-2.6!skip
+plugins/module_utils/_version.py import-2.7!skip
+plugins/module_utils/_version.py import-3.5!skip
+plugins/module_utils/copy.py import-2.6!skip
+plugins/module_utils/copy.py import-2.7!skip
+plugins/module_utils/copy.py import-3.5!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/__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/selector.py import-2.6!skip
+plugins/module_utils/selector.py import-2.7!skip
+plugins/module_utils/selector.py import-3.5!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/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/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/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/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/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/k8s/resource.py import-2.6!skip
+plugins/module_utils/k8s/resource.py import-2.7!skip
+plugins/module_utils/k8s/resource.py import-3.5!skip
+plugins/module_utils/k8s/core.py import-2.6!skip
+plugins/module_utils/k8s/core.py import-2.7!skip
+plugins/module_utils/k8s/core.py import-3.5!skip
+plugins/module_utils/k8s/waiter.py import-2.6!skip
+plugins/module_utils/k8s/waiter.py import-2.7!skip
+plugins/module_utils/k8s/waiter.py import-3.5!skip
+plugins/module_utils/k8s/client.py import-2.6!skip
+plugins/module_utils/k8s/client.py import-2.7!skip
+plugins/module_utils/k8s/client.py import-3.5!skip
+plugins/module_utils/k8s/runner.py import-2.6!skip
+plugins/module_utils/k8s/runner.py import-2.7!skip
+plugins/module_utils/k8s/runner.py import-3.5!skip
+plugins/module_utils/k8s/service.py import-2.6!skip
+plugins/module_utils/k8s/service.py import-2.7!skip
+plugins/module_utils/k8s/service.py import-3.5!skip
+plugins/module_utils/k8s/exceptions.py import-2.6!skip
+plugins/module_utils/k8s/exceptions.py import-2.7!skip
+plugins/module_utils/k8s/exceptions.py import-3.5!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_name_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_auth_options.py compile-3.5!skip
+plugins/doc_fragments/helm_common_options.py compile-2.6!skip
+plugins/doc_fragments/helm_common_options.py compile-2.7!skip
+plugins/doc_fragments/helm_common_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_state_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_wait_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_scale_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_delete_options.py compile-3.5!skip
+plugins/doc_fragments/__init__.py compile-2.6!skip
+plugins/doc_fragments/__init__.py compile-2.7!skip
+plugins/doc_fragments/__init__.py compile-3.5!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_resource_options.py compile-3.5!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/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/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/helm_args_common.py compile-2.6!skip
+plugins/module_utils/helm_args_common.py compile-2.7!skip
+plugins/module_utils/helm_args_common.py compile-3.5!skip
+plugins/module_utils/version.py compile-2.6!skip
+plugins/module_utils/version.py compile-2.7!skip
+plugins/module_utils/version.py compile-3.5!skip
+plugins/module_utils/_version.py compile-2.6!skip
+plugins/module_utils/_version.py compile-2.7!skip
+plugins/module_utils/_version.py compile-3.5!skip
+plugins/module_utils/copy.py compile-2.6!skip
+plugins/module_utils/copy.py compile-2.7!skip
+plugins/module_utils/copy.py compile-3.5!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/__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/selector.py compile-2.6!skip
+plugins/module_utils/selector.py compile-2.7!skip
+plugins/module_utils/selector.py compile-3.5!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/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/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/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/client/resource.py compile-2.6!skip
+plugins/module_utils/client/resource.py compile-2.7!skip
+plugins/module_utils/client/resource.py compile-3.5!skip
+plugins/module_utils/client/discovery.py compile-2.6!skip
+plugins/module_utils/client/discovery.py compile-2.7!skip
+plugins/module_utils/client/discovery.py compile-3.5!skip
+plugins/module_utils/k8s/resource.py compile-2.6!skip
+plugins/module_utils/k8s/resource.py compile-2.7!skip
+plugins/module_utils/k8s/resource.py compile-3.5!skip
+plugins/module_utils/k8s/core.py compile-2.6!skip
+plugins/module_utils/k8s/core.py compile-2.7!skip
+plugins/module_utils/k8s/core.py compile-3.5!skip
+plugins/module_utils/k8s/waiter.py compile-2.6!skip
+plugins/module_utils/k8s/waiter.py compile-2.7!skip
+plugins/module_utils/k8s/waiter.py compile-3.5!skip
+plugins/module_utils/k8s/client.py compile-2.6!skip
+plugins/module_utils/k8s/client.py compile-2.7!skip
+plugins/module_utils/k8s/client.py compile-3.5!skip
+plugins/module_utils/k8s/runner.py compile-2.6!skip
+plugins/module_utils/k8s/runner.py compile-2.7!skip
+plugins/module_utils/k8s/runner.py compile-3.5!skip
+plugins/module_utils/k8s/service.py compile-2.6!skip
+plugins/module_utils/k8s/service.py compile-2.7!skip
+plugins/module_utils/k8s/service.py compile-3.5!skip
+plugins/module_utils/k8s/exceptions.py compile-2.6!skip
+plugins/module_utils/k8s/exceptions.py compile-2.7!skip
+plugins/module_utils/k8s/exceptions.py compile-3.5!skip
+plugins/connection/kubectl.py compile-2.6!skip
+plugins/connection/kubectl.py compile-2.7!skip
+plugins/connection/kubectl.py compile-3.5!skip
+plugins/inventory/k8s.py compile-2.6!skip
+plugins/inventory/k8s.py compile-2.7!skip
+plugins/inventory/k8s.py compile-3.5!skip
+plugins/lookup/k8s.py compile-2.6!skip
+plugins/lookup/k8s.py compile-2.7!skip
+plugins/lookup/k8s.py compile-3.5!skip
+plugins/lookup/kustomize.py compile-2.6!skip
+plugins/lookup/kustomize.py compile-2.7!skip
+plugins/lookup/kustomize.py compile-3.5!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/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/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/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_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_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_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/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_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_drain.py compile-2.6!skip
+plugins/modules/k8s_drain.py compile-2.7!skip
+plugins/modules/k8s_drain.py compile-3.5!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/k8s_taint.py compile-2.6!skip
+plugins/modules/k8s_taint.py compile-2.7!skip
+plugins/modules/k8s_taint.py compile-3.5!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_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_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_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_cp.py compile-2.6!skip
+plugins/modules/k8s_cp.py compile-2.7!skip
+plugins/modules/k8s_cp.py compile-3.5!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/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/action/k8s_info.py compile-2.6!skip
+plugins/action/k8s_info.py compile-2.7!skip
+plugins/action/k8s_info.py compile-3.5!skip
+plugins/filter/k8s.py compile-2.6!skip
+plugins/filter/k8s.py compile-2.7!skip
+plugins/filter/k8s.py compile-3.5!skip
+tests/unit/conftest.py compile-2.6!skip
+tests/unit/conftest.py compile-2.7!skip
+tests/unit/conftest.py compile-3.5!skip
+tests/unit/utils/ansible_module_mock.py compile-2.6!skip
+tests/unit/utils/ansible_module_mock.py compile-2.7!skip
+tests/unit/utils/ansible_module_mock.py compile-3.5!skip
+tests/unit/module_utils/test_helm.py compile-2.6!skip
+tests/unit/module_utils/test_helm.py compile-2.7!skip
+tests/unit/module_utils/test_helm.py compile-3.5!skip
+tests/unit/module_utils/test_marshal.py compile-2.6!skip
+tests/unit/module_utils/test_marshal.py compile-2.7!skip
+tests/unit/module_utils/test_marshal.py compile-3.5!skip
+tests/unit/module_utils/test_discoverer.py compile-2.6!skip
+tests/unit/module_utils/test_discoverer.py compile-2.7!skip
+tests/unit/module_utils/test_discoverer.py compile-3.5!skip
+tests/unit/module_utils/test_hashes.py compile-2.6!skip
+tests/unit/module_utils/test_hashes.py compile-2.7!skip
+tests/unit/module_utils/test_hashes.py compile-3.5!skip
+tests/unit/module_utils/test_resource.py compile-2.6!skip
+tests/unit/module_utils/test_resource.py compile-2.7!skip
+tests/unit/module_utils/test_resource.py compile-3.5!skip
+tests/unit/module_utils/test_service.py compile-2.6!skip
+tests/unit/module_utils/test_service.py compile-2.7!skip
+tests/unit/module_utils/test_service.py compile-3.5!skip
+tests/unit/module_utils/test_waiter.py compile-2.6!skip
+tests/unit/module_utils/test_waiter.py compile-2.7!skip
+tests/unit/module_utils/test_waiter.py compile-3.5!skip
+tests/unit/module_utils/test_common.py compile-2.6!skip
+tests/unit/module_utils/test_common.py compile-2.7!skip
+tests/unit/module_utils/test_common.py compile-3.5!skip
+tests/unit/module_utils/test_selector.py compile-2.6!skip
+tests/unit/module_utils/test_selector.py compile-2.7!skip
+tests/unit/module_utils/test_selector.py compile-3.5!skip
+tests/unit/module_utils/test_apply.py compile-2.6!skip
+tests/unit/module_utils/test_apply.py compile-2.7!skip
+tests/unit/module_utils/test_apply.py compile-3.5!skip
+tests/unit/module_utils/test_runner.py compile-2.6!skip
+tests/unit/module_utils/test_runner.py compile-2.7!skip
+tests/unit/module_utils/test_runner.py compile-3.5!skip
+tests/unit/module_utils/test_client.py compile-2.6!skip
+tests/unit/module_utils/test_client.py compile-2.7!skip
+tests/unit/module_utils/test_client.py compile-3.5!skip
+tests/unit/module_utils/test_core.py compile-2.6!skip
+tests/unit/module_utils/test_core.py compile-2.7!skip
+tests/unit/module_utils/test_core.py compile-3.5!skip
+tests/unit/modules/test_helm_template_module.py compile-2.6!skip
+tests/unit/modules/test_helm_template_module.py compile-2.7!skip
+tests/unit/modules/test_helm_template_module.py compile-3.5!skip
+tests/unit/modules/test_helm_template.py compile-2.6!skip
+tests/unit/modules/test_helm_template.py compile-2.7!skip
+tests/unit/modules/test_helm_template.py compile-3.5!skip
+tests/unit/modules/test_module_helm.py compile-2.6!skip
+tests/unit/modules/test_module_helm.py compile-2.7!skip
+tests/unit/modules/test_module_helm.py compile-3.5!skip
+tests/unit/action/test_remove_omit.py compile-2.6!skip
+tests/unit/action/test_remove_omit.py compile-2.7!skip
+tests/unit/action/test_remove_omit.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.6!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.7!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-3.5!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.6!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.7!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-3.5!skip
+plugins/modules/k8s_scale.py pylint!skip
+plugins/modules/helm_template.py pylint!skip
+plugins/modules/k8s_exec.py pylint!skip
+plugins/modules/helm.py pylint!skip
+plugins/modules/helm_plugin_info.py pylint!skip
+plugins/modules/helm_info.py pylint!skip
+plugins/modules/helm_repository.py pylint!skip
+plugins/modules/k8s_rollback.py pylint!skip
+plugins/modules/k8s_log.py pylint!skip
+plugins/modules/k8s_drain.py pylint!skip
+plugins/modules/helm_plugin.py pylint!skip
+plugins/modules/k8s_taint.py pylint!skip
+plugins/modules/k8s.py pylint!skip
+plugins/modules/k8s_service.py pylint!skip
+plugins/modules/k8s_cluster_info.py pylint!skip
+plugins/modules/k8s_info.py pylint!skip
+plugins/modules/k8s_cp.py pylint!skip
+plugins/modules/__init__.py pylint!skip
+plugins/modules/k8s_json_patch.py pylint!skip
+plugins/module_utils/helm.py pylint!skip
+plugins/module_utils/apply.py pylint!skip
+plugins/module_utils/hashes.py pylint!skip
+plugins/module_utils/helm_args_common.py pylint!skip
+plugins/module_utils/version.py pylint!skip
+plugins/module_utils/_version.py pylint!skip
+plugins/module_utils/copy.py pylint!skip
+plugins/module_utils/args_common.py pylint!skip
+plugins/module_utils/__init__.py pylint!skip
+plugins/module_utils/selector.py pylint!skip
+plugins/module_utils/k8sdynamicclient.py pylint!skip
+plugins/module_utils/common.py pylint!skip
+plugins/module_utils/ansiblemodule.py pylint!skip
+plugins/module_utils/exceptions.py pylint!skip
+plugins/module_utils/client/resource.py pylint!skip
+plugins/module_utils/client/discovery.py pylint!skip
+plugins/module_utils/k8s/resource.py pylint!skip
+plugins/module_utils/k8s/core.py pylint!skip
+plugins/module_utils/k8s/waiter.py pylint!skip
+plugins/module_utils/k8s/client.py pylint!skip
+plugins/module_utils/k8s/runner.py pylint!skip
+plugins/module_utils/k8s/service.py pylint!skip
+plugins/module_utils/k8s/exceptions.py pylint!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py pylint!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py pylint!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py pylint!skip
+tests/integration/targets/helm/library/helm_test_version.py pylint!skip
+tests/unit/conftest.py pylint!skip
+tests/unit/utils/ansible_module_mock.py pylint!skip
+tests/unit/module_utils/test_helm.py pylint!skip
+tests/unit/module_utils/test_marshal.py pylint!skip
+tests/unit/module_utils/test_discoverer.py pylint!skip
+tests/unit/module_utils/test_hashes.py pylint!skip
+tests/unit/module_utils/test_resource.py pylint!skip
+tests/unit/module_utils/test_service.py pylint!skip
+tests/unit/module_utils/test_waiter.py pylint!skip
+tests/unit/module_utils/test_common.py pylint!skip
+tests/unit/module_utils/test_selector.py pylint!skip
+tests/unit/module_utils/test_apply.py pylint!skip
+tests/unit/module_utils/test_runner.py pylint!skip
+tests/unit/module_utils/test_client.py pylint!skip
+tests/unit/module_utils/test_core.py pylint!skip
+tests/unit/modules/test_helm_template_module.py pylint!skip
+tests/unit/modules/test_helm_template.py pylint!skip
+tests/unit/modules/test_module_helm.py pylint!skip
+tests/unit/action/test_remove_omit.py pylint!skip
+plugins/modules/k8s.py validate-modules!skip
+plugins/modules/k8s_cp.py validate-modules!skip
+plugins/modules/k8s_drain.py validate-modules!skip
+plugins/modules/k8s_exec.py validate-modules!skip
+plugins/modules/k8s_info.py validate-modules!skip
+plugins/modules/k8s_json_patch.py validate-modules!skip
+plugins/modules/k8s_log.py validate-modules!skip
+plugins/modules/k8s_rollback.py validate-modules!skip
+plugins/modules/k8s_scale.py validate-modules!skip
+plugins/modules/k8s_service.py validate-modules!skip
+plugins/modules/k8s_taint.py validate-modules!skip
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.11.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.11.txt
new file mode 100644
index 00000000..80122d80
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.11.txt
@@ -0,0 +1,592 @@
+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/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/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/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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/modules/k8s.py validate-modules:return-syntax-error
+plugins/modules/k8s_scale.py validate-modules:return-syntax-error
+plugins/modules/k8s_service.py validate-modules:return-syntax-error
+plugins/modules/k8s_taint.py validate-modules:return-syntax-error
+plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip
+plugins/doc_fragments/__init__.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip
+plugins/module_utils/helm.py future-import-boilerplate!skip
+plugins/module_utils/apply.py future-import-boilerplate!skip
+plugins/module_utils/hashes.py future-import-boilerplate!skip
+plugins/module_utils/helm_args_common.py future-import-boilerplate!skip
+plugins/module_utils/version.py future-import-boilerplate!skip
+plugins/module_utils/_version.py future-import-boilerplate!skip
+plugins/module_utils/copy.py future-import-boilerplate!skip
+plugins/module_utils/args_common.py future-import-boilerplate!skip
+plugins/module_utils/__init__.py future-import-boilerplate!skip
+plugins/module_utils/selector.py future-import-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
+plugins/module_utils/common.py future-import-boilerplate!skip
+plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
+plugins/module_utils/exceptions.py future-import-boilerplate!skip
+plugins/module_utils/client/resource.py future-import-boilerplate!skip
+plugins/module_utils/client/discovery.py future-import-boilerplate!skip
+plugins/module_utils/k8s/resource.py future-import-boilerplate!skip
+plugins/module_utils/k8s/core.py future-import-boilerplate!skip
+plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip
+plugins/module_utils/k8s/client.py future-import-boilerplate!skip
+plugins/module_utils/k8s/runner.py future-import-boilerplate!skip
+plugins/module_utils/k8s/service.py future-import-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip
+plugins/connection/kubectl.py future-import-boilerplate!skip
+plugins/inventory/k8s.py future-import-boilerplate!skip
+plugins/lookup/k8s.py future-import-boilerplate!skip
+plugins/lookup/kustomize.py future-import-boilerplate!skip
+plugins/modules/k8s_scale.py future-import-boilerplate!skip
+plugins/modules/helm_template.py future-import-boilerplate!skip
+plugins/modules/k8s_exec.py future-import-boilerplate!skip
+plugins/modules/helm.py future-import-boilerplate!skip
+plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
+plugins/modules/helm_info.py future-import-boilerplate!skip
+plugins/modules/helm_repository.py future-import-boilerplate!skip
+plugins/modules/k8s_rollback.py future-import-boilerplate!skip
+plugins/modules/k8s_log.py future-import-boilerplate!skip
+plugins/modules/k8s_drain.py future-import-boilerplate!skip
+plugins/modules/helm_plugin.py future-import-boilerplate!skip
+plugins/modules/k8s_taint.py future-import-boilerplate!skip
+plugins/modules/k8s.py future-import-boilerplate!skip
+plugins/modules/k8s_service.py future-import-boilerplate!skip
+plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
+plugins/modules/k8s_info.py future-import-boilerplate!skip
+plugins/modules/k8s_cp.py future-import-boilerplate!skip
+plugins/modules/__init__.py future-import-boilerplate!skip
+plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
+plugins/action/k8s_info.py future-import-boilerplate!skip
+plugins/filter/k8s.py future-import-boilerplate!skip
+tests/unit/conftest.py future-import-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip
+tests/unit/module_utils/test_helm.py future-import-boilerplate!skip
+tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
+tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip
+tests/unit/module_utils/test_resource.py future-import-boilerplate!skip
+tests/unit/module_utils/test_service.py future-import-boilerplate!skip
+tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip
+tests/unit/module_utils/test_common.py future-import-boilerplate!skip
+tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
+tests/unit/module_utils/test_apply.py future-import-boilerplate!skip
+tests/unit/module_utils/test_runner.py future-import-boilerplate!skip
+tests/unit/module_utils/test_client.py future-import-boilerplate!skip
+tests/unit/module_utils/test_core.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template.py future-import-boilerplate!skip
+tests/unit/modules/test_module_helm.py future-import-boilerplate!skip
+tests/unit/action/test_remove_omit.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/__init__.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip
+plugins/module_utils/helm.py metaclass-boilerplate!skip
+plugins/module_utils/apply.py metaclass-boilerplate!skip
+plugins/module_utils/hashes.py metaclass-boilerplate!skip
+plugins/module_utils/helm_args_common.py metaclass-boilerplate!skip
+plugins/module_utils/version.py metaclass-boilerplate!skip
+plugins/module_utils/_version.py metaclass-boilerplate!skip
+plugins/module_utils/copy.py metaclass-boilerplate!skip
+plugins/module_utils/args_common.py metaclass-boilerplate!skip
+plugins/module_utils/__init__.py metaclass-boilerplate!skip
+plugins/module_utils/selector.py metaclass-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
+plugins/module_utils/common.py metaclass-boilerplate!skip
+plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
+plugins/module_utils/exceptions.py metaclass-boilerplate!skip
+plugins/module_utils/client/resource.py metaclass-boilerplate!skip
+plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/core.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/client.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/service.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip
+plugins/connection/kubectl.py metaclass-boilerplate!skip
+plugins/inventory/k8s.py metaclass-boilerplate!skip
+plugins/lookup/k8s.py metaclass-boilerplate!skip
+plugins/lookup/kustomize.py metaclass-boilerplate!skip
+plugins/modules/k8s_scale.py metaclass-boilerplate!skip
+plugins/modules/helm_template.py metaclass-boilerplate!skip
+plugins/modules/k8s_exec.py metaclass-boilerplate!skip
+plugins/modules/helm.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
+plugins/modules/helm_info.py metaclass-boilerplate!skip
+plugins/modules/helm_repository.py metaclass-boilerplate!skip
+plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
+plugins/modules/k8s_log.py metaclass-boilerplate!skip
+plugins/modules/k8s_drain.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin.py metaclass-boilerplate!skip
+plugins/modules/k8s_taint.py metaclass-boilerplate!skip
+plugins/modules/k8s.py metaclass-boilerplate!skip
+plugins/modules/k8s_service.py metaclass-boilerplate!skip
+plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_cp.py metaclass-boilerplate!skip
+plugins/modules/__init__.py metaclass-boilerplate!skip
+plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
+plugins/action/k8s_info.py metaclass-boilerplate!skip
+plugins/filter/k8s.py metaclass-boilerplate!skip
+tests/unit/conftest.py metaclass-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_service.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_common.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_client.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_core.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip
+tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip
+tests/unit/action/test_remove_omit.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_name_options.py import-2.6!skip
+plugins/doc_fragments/k8s_name_options.py import-2.7!skip
+plugins/doc_fragments/k8s_name_options.py import-3.5!skip
+plugins/doc_fragments/k8s_auth_options.py import-2.6!skip
+plugins/doc_fragments/k8s_auth_options.py import-2.7!skip
+plugins/doc_fragments/k8s_auth_options.py import-3.5!skip
+plugins/doc_fragments/helm_common_options.py import-2.6!skip
+plugins/doc_fragments/helm_common_options.py import-2.7!skip
+plugins/doc_fragments/helm_common_options.py import-3.5!skip
+plugins/doc_fragments/k8s_state_options.py import-2.6!skip
+plugins/doc_fragments/k8s_state_options.py import-2.7!skip
+plugins/doc_fragments/k8s_state_options.py import-3.5!skip
+plugins/doc_fragments/k8s_wait_options.py import-2.6!skip
+plugins/doc_fragments/k8s_wait_options.py import-2.7!skip
+plugins/doc_fragments/k8s_wait_options.py import-3.5!skip
+plugins/doc_fragments/k8s_scale_options.py import-2.6!skip
+plugins/doc_fragments/k8s_scale_options.py import-2.7!skip
+plugins/doc_fragments/k8s_scale_options.py import-3.5!skip
+plugins/doc_fragments/k8s_delete_options.py import-2.6!skip
+plugins/doc_fragments/k8s_delete_options.py import-2.7!skip
+plugins/doc_fragments/k8s_delete_options.py import-3.5!skip
+plugins/doc_fragments/__init__.py import-2.6!skip
+plugins/doc_fragments/__init__.py import-2.7!skip
+plugins/doc_fragments/__init__.py import-3.5!skip
+plugins/doc_fragments/k8s_resource_options.py import-2.6!skip
+plugins/doc_fragments/k8s_resource_options.py import-2.7!skip
+plugins/doc_fragments/k8s_resource_options.py import-3.5!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/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/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/helm_args_common.py import-2.6!skip
+plugins/module_utils/helm_args_common.py import-2.7!skip
+plugins/module_utils/helm_args_common.py import-3.5!skip
+plugins/module_utils/version.py import-2.6!skip
+plugins/module_utils/version.py import-2.7!skip
+plugins/module_utils/version.py import-3.5!skip
+plugins/module_utils/_version.py import-2.6!skip
+plugins/module_utils/_version.py import-2.7!skip
+plugins/module_utils/_version.py import-3.5!skip
+plugins/module_utils/copy.py import-2.6!skip
+plugins/module_utils/copy.py import-2.7!skip
+plugins/module_utils/copy.py import-3.5!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/__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/selector.py import-2.6!skip
+plugins/module_utils/selector.py import-2.7!skip
+plugins/module_utils/selector.py import-3.5!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/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/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/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/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/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/k8s/resource.py import-2.6!skip
+plugins/module_utils/k8s/resource.py import-2.7!skip
+plugins/module_utils/k8s/resource.py import-3.5!skip
+plugins/module_utils/k8s/core.py import-2.6!skip
+plugins/module_utils/k8s/core.py import-2.7!skip
+plugins/module_utils/k8s/core.py import-3.5!skip
+plugins/module_utils/k8s/waiter.py import-2.6!skip
+plugins/module_utils/k8s/waiter.py import-2.7!skip
+plugins/module_utils/k8s/waiter.py import-3.5!skip
+plugins/module_utils/k8s/client.py import-2.6!skip
+plugins/module_utils/k8s/client.py import-2.7!skip
+plugins/module_utils/k8s/client.py import-3.5!skip
+plugins/module_utils/k8s/runner.py import-2.6!skip
+plugins/module_utils/k8s/runner.py import-2.7!skip
+plugins/module_utils/k8s/runner.py import-3.5!skip
+plugins/module_utils/k8s/service.py import-2.6!skip
+plugins/module_utils/k8s/service.py import-2.7!skip
+plugins/module_utils/k8s/service.py import-3.5!skip
+plugins/module_utils/k8s/exceptions.py import-2.6!skip
+plugins/module_utils/k8s/exceptions.py import-2.7!skip
+plugins/module_utils/k8s/exceptions.py import-3.5!skip
+plugins/connection/kubectl.py import-2.6!skip
+plugins/connection/kubectl.py import-2.7!skip
+plugins/connection/kubectl.py import-3.5!skip
+plugins/inventory/k8s.py import-2.6!skip
+plugins/inventory/k8s.py import-2.7!skip
+plugins/inventory/k8s.py import-3.5!skip
+plugins/lookup/k8s.py import-2.6!skip
+plugins/lookup/k8s.py import-2.7!skip
+plugins/lookup/k8s.py import-3.5!skip
+plugins/lookup/kustomize.py import-2.6!skip
+plugins/lookup/kustomize.py import-2.7!skip
+plugins/lookup/kustomize.py import-3.5!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/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/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/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_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_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_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/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_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_drain.py import-2.6!skip
+plugins/modules/k8s_drain.py import-2.7!skip
+plugins/modules/k8s_drain.py import-3.5!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/k8s_taint.py import-2.6!skip
+plugins/modules/k8s_taint.py import-2.7!skip
+plugins/modules/k8s_taint.py import-3.5!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_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_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_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_cp.py import-2.6!skip
+plugins/modules/k8s_cp.py import-2.7!skip
+plugins/modules/k8s_cp.py import-3.5!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/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/action/k8s_info.py import-2.6!skip
+plugins/action/k8s_info.py import-2.7!skip
+plugins/action/k8s_info.py import-3.5!skip
+plugins/filter/k8s.py import-2.6!skip
+plugins/filter/k8s.py import-2.7!skip
+plugins/filter/k8s.py import-3.5!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_name_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_auth_options.py compile-3.5!skip
+plugins/doc_fragments/helm_common_options.py compile-2.6!skip
+plugins/doc_fragments/helm_common_options.py compile-2.7!skip
+plugins/doc_fragments/helm_common_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_state_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_wait_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_scale_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_delete_options.py compile-3.5!skip
+plugins/doc_fragments/__init__.py compile-2.6!skip
+plugins/doc_fragments/__init__.py compile-2.7!skip
+plugins/doc_fragments/__init__.py compile-3.5!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_resource_options.py compile-3.5!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/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/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/helm_args_common.py compile-2.6!skip
+plugins/module_utils/helm_args_common.py compile-2.7!skip
+plugins/module_utils/helm_args_common.py compile-3.5!skip
+plugins/module_utils/version.py compile-2.6!skip
+plugins/module_utils/version.py compile-2.7!skip
+plugins/module_utils/version.py compile-3.5!skip
+plugins/module_utils/_version.py compile-2.6!skip
+plugins/module_utils/_version.py compile-2.7!skip
+plugins/module_utils/_version.py compile-3.5!skip
+plugins/module_utils/copy.py compile-2.6!skip
+plugins/module_utils/copy.py compile-2.7!skip
+plugins/module_utils/copy.py compile-3.5!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/__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/selector.py compile-2.6!skip
+plugins/module_utils/selector.py compile-2.7!skip
+plugins/module_utils/selector.py compile-3.5!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/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/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/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/client/resource.py compile-2.6!skip
+plugins/module_utils/client/resource.py compile-2.7!skip
+plugins/module_utils/client/resource.py compile-3.5!skip
+plugins/module_utils/client/discovery.py compile-2.6!skip
+plugins/module_utils/client/discovery.py compile-2.7!skip
+plugins/module_utils/client/discovery.py compile-3.5!skip
+plugins/module_utils/k8s/resource.py compile-2.6!skip
+plugins/module_utils/k8s/resource.py compile-2.7!skip
+plugins/module_utils/k8s/resource.py compile-3.5!skip
+plugins/module_utils/k8s/core.py compile-2.6!skip
+plugins/module_utils/k8s/core.py compile-2.7!skip
+plugins/module_utils/k8s/core.py compile-3.5!skip
+plugins/module_utils/k8s/waiter.py compile-2.6!skip
+plugins/module_utils/k8s/waiter.py compile-2.7!skip
+plugins/module_utils/k8s/waiter.py compile-3.5!skip
+plugins/module_utils/k8s/client.py compile-2.6!skip
+plugins/module_utils/k8s/client.py compile-2.7!skip
+plugins/module_utils/k8s/client.py compile-3.5!skip
+plugins/module_utils/k8s/runner.py compile-2.6!skip
+plugins/module_utils/k8s/runner.py compile-2.7!skip
+plugins/module_utils/k8s/runner.py compile-3.5!skip
+plugins/module_utils/k8s/service.py compile-2.6!skip
+plugins/module_utils/k8s/service.py compile-2.7!skip
+plugins/module_utils/k8s/service.py compile-3.5!skip
+plugins/module_utils/k8s/exceptions.py compile-2.6!skip
+plugins/module_utils/k8s/exceptions.py compile-2.7!skip
+plugins/module_utils/k8s/exceptions.py compile-3.5!skip
+plugins/connection/kubectl.py compile-2.6!skip
+plugins/connection/kubectl.py compile-2.7!skip
+plugins/connection/kubectl.py compile-3.5!skip
+plugins/inventory/k8s.py compile-2.6!skip
+plugins/inventory/k8s.py compile-2.7!skip
+plugins/inventory/k8s.py compile-3.5!skip
+plugins/lookup/k8s.py compile-2.6!skip
+plugins/lookup/k8s.py compile-2.7!skip
+plugins/lookup/k8s.py compile-3.5!skip
+plugins/lookup/kustomize.py compile-2.6!skip
+plugins/lookup/kustomize.py compile-2.7!skip
+plugins/lookup/kustomize.py compile-3.5!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/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/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/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_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_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_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/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_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_drain.py compile-2.6!skip
+plugins/modules/k8s_drain.py compile-2.7!skip
+plugins/modules/k8s_drain.py compile-3.5!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/k8s_taint.py compile-2.6!skip
+plugins/modules/k8s_taint.py compile-2.7!skip
+plugins/modules/k8s_taint.py compile-3.5!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_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_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_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_cp.py compile-2.6!skip
+plugins/modules/k8s_cp.py compile-2.7!skip
+plugins/modules/k8s_cp.py compile-3.5!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/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/action/k8s_info.py compile-2.6!skip
+plugins/action/k8s_info.py compile-2.7!skip
+plugins/action/k8s_info.py compile-3.5!skip
+plugins/filter/k8s.py compile-2.6!skip
+plugins/filter/k8s.py compile-2.7!skip
+plugins/filter/k8s.py compile-3.5!skip
+tests/unit/conftest.py compile-2.6!skip
+tests/unit/conftest.py compile-2.7!skip
+tests/unit/conftest.py compile-3.5!skip
+tests/unit/utils/ansible_module_mock.py compile-2.6!skip
+tests/unit/utils/ansible_module_mock.py compile-2.7!skip
+tests/unit/utils/ansible_module_mock.py compile-3.5!skip
+tests/unit/module_utils/test_helm.py compile-2.6!skip
+tests/unit/module_utils/test_helm.py compile-2.7!skip
+tests/unit/module_utils/test_helm.py compile-3.5!skip
+tests/unit/module_utils/test_marshal.py compile-2.6!skip
+tests/unit/module_utils/test_marshal.py compile-2.7!skip
+tests/unit/module_utils/test_marshal.py compile-3.5!skip
+tests/unit/module_utils/test_discoverer.py compile-2.6!skip
+tests/unit/module_utils/test_discoverer.py compile-2.7!skip
+tests/unit/module_utils/test_discoverer.py compile-3.5!skip
+tests/unit/module_utils/test_hashes.py compile-2.6!skip
+tests/unit/module_utils/test_hashes.py compile-2.7!skip
+tests/unit/module_utils/test_hashes.py compile-3.5!skip
+tests/unit/module_utils/test_resource.py compile-2.6!skip
+tests/unit/module_utils/test_resource.py compile-2.7!skip
+tests/unit/module_utils/test_resource.py compile-3.5!skip
+tests/unit/module_utils/test_service.py compile-2.6!skip
+tests/unit/module_utils/test_service.py compile-2.7!skip
+tests/unit/module_utils/test_service.py compile-3.5!skip
+tests/unit/module_utils/test_waiter.py compile-2.6!skip
+tests/unit/module_utils/test_waiter.py compile-2.7!skip
+tests/unit/module_utils/test_waiter.py compile-3.5!skip
+tests/unit/module_utils/test_common.py compile-2.6!skip
+tests/unit/module_utils/test_common.py compile-2.7!skip
+tests/unit/module_utils/test_common.py compile-3.5!skip
+tests/unit/module_utils/test_selector.py compile-2.6!skip
+tests/unit/module_utils/test_selector.py compile-2.7!skip
+tests/unit/module_utils/test_selector.py compile-3.5!skip
+tests/unit/module_utils/test_apply.py compile-2.6!skip
+tests/unit/module_utils/test_apply.py compile-2.7!skip
+tests/unit/module_utils/test_apply.py compile-3.5!skip
+tests/unit/module_utils/test_runner.py compile-2.6!skip
+tests/unit/module_utils/test_runner.py compile-2.7!skip
+tests/unit/module_utils/test_runner.py compile-3.5!skip
+tests/unit/module_utils/test_client.py compile-2.6!skip
+tests/unit/module_utils/test_client.py compile-2.7!skip
+tests/unit/module_utils/test_client.py compile-3.5!skip
+tests/unit/module_utils/test_core.py compile-2.6!skip
+tests/unit/module_utils/test_core.py compile-2.7!skip
+tests/unit/module_utils/test_core.py compile-3.5!skip
+tests/unit/modules/test_helm_template_module.py compile-2.6!skip
+tests/unit/modules/test_helm_template_module.py compile-2.7!skip
+tests/unit/modules/test_helm_template_module.py compile-3.5!skip
+tests/unit/modules/test_helm_template.py compile-2.6!skip
+tests/unit/modules/test_helm_template.py compile-2.7!skip
+tests/unit/modules/test_helm_template.py compile-3.5!skip
+tests/unit/modules/test_module_helm.py compile-2.6!skip
+tests/unit/modules/test_module_helm.py compile-2.7!skip
+tests/unit/modules/test_module_helm.py compile-3.5!skip
+tests/unit/action/test_remove_omit.py compile-2.6!skip
+tests/unit/action/test_remove_omit.py compile-2.7!skip
+tests/unit/action/test_remove_omit.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.6!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.7!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-3.5!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.6!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.7!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-3.5!skip
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.12.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.12.txt
new file mode 100644
index 00000000..6cfa02f6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.12.txt
@@ -0,0 +1,32 @@
+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/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/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/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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/modules/k8s.py validate-modules:return-syntax-error
+plugins/modules/k8s_scale.py validate-modules:return-syntax-error
+plugins/modules/k8s_service.py validate-modules:return-syntax-error
+plugins/modules/k8s_taint.py validate-modules:return-syntax-error
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.13.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.13.txt
new file mode 100644
index 00000000..6cfa02f6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.13.txt
@@ -0,0 +1,32 @@
+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/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/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/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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/modules/k8s.py validate-modules:return-syntax-error
+plugins/modules/k8s_scale.py validate-modules:return-syntax-error
+plugins/modules/k8s_service.py validate-modules:return-syntax-error
+plugins/modules/k8s_taint.py validate-modules:return-syntax-error
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.14.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.14.txt
new file mode 100644
index 00000000..6cfa02f6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.14.txt
@@ -0,0 +1,32 @@
+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/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/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/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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/modules/k8s.py validate-modules:return-syntax-error
+plugins/modules/k8s_scale.py validate-modules:return-syntax-error
+plugins/modules/k8s_service.py validate-modules:return-syntax-error
+plugins/modules/k8s_taint.py validate-modules:return-syntax-error
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.15.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.15.txt
new file mode 100644
index 00000000..95cd652a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.15.txt
@@ -0,0 +1,35 @@
+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 import-3.11!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/client/resource.py import-3.11!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 import-3.11!skip
+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
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/modules/k8s.py validate-modules:return-syntax-error
+plugins/modules/k8s_scale.py validate-modules:return-syntax-error
+plugins/modules/k8s_service.py validate-modules:return-syntax-error
+plugins/modules/k8s_taint.py validate-modules:return-syntax-error
diff --git a/ansible_collections/kubernetes/core/tests/sanity/ignore-2.9.txt b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.9.txt
new file mode 100644
index 00000000..7828a0c0
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/ignore-2.9.txt
@@ -0,0 +1,613 @@
+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/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/k8sdynamicclient.py import-3.6!skip
+plugins/module_utils/k8sdynamicclient.py import-3.7!skip
+plugins/module_utils/k8sdynamicclient.py import-3.8!skip
+tests/unit/module_utils/fixtures/definitions.yml yamllint!skip
+tests/unit/module_utils/fixtures/deployments.yml yamllint!skip
+tests/unit/module_utils/fixtures/pods.yml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml yamllint!skip
+tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip
+tests/sanity/refresh_ignore_files shebang!skip
+plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip
+plugins/doc_fragments/__init__.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip
+plugins/module_utils/helm.py future-import-boilerplate!skip
+plugins/module_utils/apply.py future-import-boilerplate!skip
+plugins/module_utils/hashes.py future-import-boilerplate!skip
+plugins/module_utils/helm_args_common.py future-import-boilerplate!skip
+plugins/module_utils/version.py future-import-boilerplate!skip
+plugins/module_utils/_version.py future-import-boilerplate!skip
+plugins/module_utils/copy.py future-import-boilerplate!skip
+plugins/module_utils/args_common.py future-import-boilerplate!skip
+plugins/module_utils/__init__.py future-import-boilerplate!skip
+plugins/module_utils/selector.py future-import-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip
+plugins/module_utils/common.py future-import-boilerplate!skip
+plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip
+plugins/module_utils/exceptions.py future-import-boilerplate!skip
+plugins/module_utils/client/resource.py future-import-boilerplate!skip
+plugins/module_utils/client/discovery.py future-import-boilerplate!skip
+plugins/module_utils/k8s/resource.py future-import-boilerplate!skip
+plugins/module_utils/k8s/core.py future-import-boilerplate!skip
+plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip
+plugins/module_utils/k8s/client.py future-import-boilerplate!skip
+plugins/module_utils/k8s/runner.py future-import-boilerplate!skip
+plugins/module_utils/k8s/service.py future-import-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip
+plugins/connection/kubectl.py future-import-boilerplate!skip
+plugins/inventory/k8s.py future-import-boilerplate!skip
+plugins/lookup/k8s.py future-import-boilerplate!skip
+plugins/lookup/kustomize.py future-import-boilerplate!skip
+plugins/modules/k8s_scale.py future-import-boilerplate!skip
+plugins/modules/helm_template.py future-import-boilerplate!skip
+plugins/modules/k8s_exec.py future-import-boilerplate!skip
+plugins/modules/helm.py future-import-boilerplate!skip
+plugins/modules/helm_plugin_info.py future-import-boilerplate!skip
+plugins/modules/helm_info.py future-import-boilerplate!skip
+plugins/modules/helm_repository.py future-import-boilerplate!skip
+plugins/modules/k8s_rollback.py future-import-boilerplate!skip
+plugins/modules/k8s_log.py future-import-boilerplate!skip
+plugins/modules/k8s_drain.py future-import-boilerplate!skip
+plugins/modules/helm_plugin.py future-import-boilerplate!skip
+plugins/modules/k8s_taint.py future-import-boilerplate!skip
+plugins/modules/k8s.py future-import-boilerplate!skip
+plugins/modules/k8s_service.py future-import-boilerplate!skip
+plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip
+plugins/modules/k8s_info.py future-import-boilerplate!skip
+plugins/modules/k8s_cp.py future-import-boilerplate!skip
+plugins/modules/__init__.py future-import-boilerplate!skip
+plugins/modules/k8s_json_patch.py future-import-boilerplate!skip
+plugins/action/k8s_info.py future-import-boilerplate!skip
+plugins/filter/k8s.py future-import-boilerplate!skip
+tests/unit/conftest.py future-import-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip
+tests/unit/module_utils/test_helm.py future-import-boilerplate!skip
+tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip
+tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip
+tests/unit/module_utils/test_resource.py future-import-boilerplate!skip
+tests/unit/module_utils/test_service.py future-import-boilerplate!skip
+tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip
+tests/unit/module_utils/test_common.py future-import-boilerplate!skip
+tests/unit/module_utils/test_selector.py future-import-boilerplate!skip
+tests/unit/module_utils/test_apply.py future-import-boilerplate!skip
+tests/unit/module_utils/test_runner.py future-import-boilerplate!skip
+tests/unit/module_utils/test_client.py future-import-boilerplate!skip
+tests/unit/module_utils/test_core.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip
+tests/unit/modules/test_helm_template.py future-import-boilerplate!skip
+tests/unit/modules/test_module_helm.py future-import-boilerplate!skip
+tests/unit/action/test_remove_omit.py future-import-boilerplate!skip
+plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip
+plugins/doc_fragments/__init__.py metaclass-boilerplate!skip
+plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip
+plugins/module_utils/helm.py metaclass-boilerplate!skip
+plugins/module_utils/apply.py metaclass-boilerplate!skip
+plugins/module_utils/hashes.py metaclass-boilerplate!skip
+plugins/module_utils/helm_args_common.py metaclass-boilerplate!skip
+plugins/module_utils/version.py metaclass-boilerplate!skip
+plugins/module_utils/_version.py metaclass-boilerplate!skip
+plugins/module_utils/copy.py metaclass-boilerplate!skip
+plugins/module_utils/args_common.py metaclass-boilerplate!skip
+plugins/module_utils/__init__.py metaclass-boilerplate!skip
+plugins/module_utils/selector.py metaclass-boilerplate!skip
+plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip
+plugins/module_utils/common.py metaclass-boilerplate!skip
+plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip
+plugins/module_utils/exceptions.py metaclass-boilerplate!skip
+plugins/module_utils/client/resource.py metaclass-boilerplate!skip
+plugins/module_utils/client/discovery.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/core.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/client.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/service.py metaclass-boilerplate!skip
+plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip
+plugins/connection/kubectl.py metaclass-boilerplate!skip
+plugins/inventory/k8s.py metaclass-boilerplate!skip
+plugins/lookup/k8s.py metaclass-boilerplate!skip
+plugins/lookup/kustomize.py metaclass-boilerplate!skip
+plugins/modules/k8s_scale.py metaclass-boilerplate!skip
+plugins/modules/helm_template.py metaclass-boilerplate!skip
+plugins/modules/k8s_exec.py metaclass-boilerplate!skip
+plugins/modules/helm.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip
+plugins/modules/helm_info.py metaclass-boilerplate!skip
+plugins/modules/helm_repository.py metaclass-boilerplate!skip
+plugins/modules/k8s_rollback.py metaclass-boilerplate!skip
+plugins/modules/k8s_log.py metaclass-boilerplate!skip
+plugins/modules/k8s_drain.py metaclass-boilerplate!skip
+plugins/modules/helm_plugin.py metaclass-boilerplate!skip
+plugins/modules/k8s_taint.py metaclass-boilerplate!skip
+plugins/modules/k8s.py metaclass-boilerplate!skip
+plugins/modules/k8s_service.py metaclass-boilerplate!skip
+plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_info.py metaclass-boilerplate!skip
+plugins/modules/k8s_cp.py metaclass-boilerplate!skip
+plugins/modules/__init__.py metaclass-boilerplate!skip
+plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip
+plugins/action/k8s_info.py metaclass-boilerplate!skip
+plugins/filter/k8s.py metaclass-boilerplate!skip
+tests/unit/conftest.py metaclass-boilerplate!skip
+tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_service.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_common.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_client.py metaclass-boilerplate!skip
+tests/unit/module_utils/test_core.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip
+tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip
+tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip
+tests/unit/action/test_remove_omit.py metaclass-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/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/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/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_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_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_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/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_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_drain.py import-2.6!skip
+plugins/modules/k8s_drain.py import-2.7!skip
+plugins/modules/k8s_drain.py import-3.5!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/k8s_taint.py import-2.6!skip
+plugins/modules/k8s_taint.py import-2.7!skip
+plugins/modules/k8s_taint.py import-3.5!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_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_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_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_cp.py import-2.6!skip
+plugins/modules/k8s_cp.py import-2.7!skip
+plugins/modules/k8s_cp.py import-3.5!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/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/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/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/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/helm_args_common.py import-2.6!skip
+plugins/module_utils/helm_args_common.py import-2.7!skip
+plugins/module_utils/helm_args_common.py import-3.5!skip
+plugins/module_utils/version.py import-2.6!skip
+plugins/module_utils/version.py import-2.7!skip
+plugins/module_utils/version.py import-3.5!skip
+plugins/module_utils/_version.py import-2.6!skip
+plugins/module_utils/_version.py import-2.7!skip
+plugins/module_utils/_version.py import-3.5!skip
+plugins/module_utils/copy.py import-2.6!skip
+plugins/module_utils/copy.py import-2.7!skip
+plugins/module_utils/copy.py import-3.5!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/__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/selector.py import-2.6!skip
+plugins/module_utils/selector.py import-2.7!skip
+plugins/module_utils/selector.py import-3.5!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/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/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/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/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/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/k8s/resource.py import-2.6!skip
+plugins/module_utils/k8s/resource.py import-2.7!skip
+plugins/module_utils/k8s/resource.py import-3.5!skip
+plugins/module_utils/k8s/core.py import-2.6!skip
+plugins/module_utils/k8s/core.py import-2.7!skip
+plugins/module_utils/k8s/core.py import-3.5!skip
+plugins/module_utils/k8s/waiter.py import-2.6!skip
+plugins/module_utils/k8s/waiter.py import-2.7!skip
+plugins/module_utils/k8s/waiter.py import-3.5!skip
+plugins/module_utils/k8s/client.py import-2.6!skip
+plugins/module_utils/k8s/client.py import-2.7!skip
+plugins/module_utils/k8s/client.py import-3.5!skip
+plugins/module_utils/k8s/runner.py import-2.6!skip
+plugins/module_utils/k8s/runner.py import-2.7!skip
+plugins/module_utils/k8s/runner.py import-3.5!skip
+plugins/module_utils/k8s/service.py import-2.6!skip
+plugins/module_utils/k8s/service.py import-2.7!skip
+plugins/module_utils/k8s/service.py import-3.5!skip
+plugins/module_utils/k8s/exceptions.py import-2.6!skip
+plugins/module_utils/k8s/exceptions.py import-2.7!skip
+plugins/module_utils/k8s/exceptions.py import-3.5!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_name_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_name_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_auth_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_auth_options.py compile-3.5!skip
+plugins/doc_fragments/helm_common_options.py compile-2.6!skip
+plugins/doc_fragments/helm_common_options.py compile-2.7!skip
+plugins/doc_fragments/helm_common_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_state_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_state_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_wait_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_wait_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_scale_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_scale_options.py compile-3.5!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_delete_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_delete_options.py compile-3.5!skip
+plugins/doc_fragments/__init__.py compile-2.6!skip
+plugins/doc_fragments/__init__.py compile-2.7!skip
+plugins/doc_fragments/__init__.py compile-3.5!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.6!skip
+plugins/doc_fragments/k8s_resource_options.py compile-2.7!skip
+plugins/doc_fragments/k8s_resource_options.py compile-3.5!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/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/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/helm_args_common.py compile-2.6!skip
+plugins/module_utils/helm_args_common.py compile-2.7!skip
+plugins/module_utils/helm_args_common.py compile-3.5!skip
+plugins/module_utils/version.py compile-2.6!skip
+plugins/module_utils/version.py compile-2.7!skip
+plugins/module_utils/version.py compile-3.5!skip
+plugins/module_utils/_version.py compile-2.6!skip
+plugins/module_utils/_version.py compile-2.7!skip
+plugins/module_utils/_version.py compile-3.5!skip
+plugins/module_utils/copy.py compile-2.6!skip
+plugins/module_utils/copy.py compile-2.7!skip
+plugins/module_utils/copy.py compile-3.5!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/__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/selector.py compile-2.6!skip
+plugins/module_utils/selector.py compile-2.7!skip
+plugins/module_utils/selector.py compile-3.5!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/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/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/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/client/resource.py compile-2.6!skip
+plugins/module_utils/client/resource.py compile-2.7!skip
+plugins/module_utils/client/resource.py compile-3.5!skip
+plugins/module_utils/client/discovery.py compile-2.6!skip
+plugins/module_utils/client/discovery.py compile-2.7!skip
+plugins/module_utils/client/discovery.py compile-3.5!skip
+plugins/module_utils/k8s/resource.py compile-2.6!skip
+plugins/module_utils/k8s/resource.py compile-2.7!skip
+plugins/module_utils/k8s/resource.py compile-3.5!skip
+plugins/module_utils/k8s/core.py compile-2.6!skip
+plugins/module_utils/k8s/core.py compile-2.7!skip
+plugins/module_utils/k8s/core.py compile-3.5!skip
+plugins/module_utils/k8s/waiter.py compile-2.6!skip
+plugins/module_utils/k8s/waiter.py compile-2.7!skip
+plugins/module_utils/k8s/waiter.py compile-3.5!skip
+plugins/module_utils/k8s/client.py compile-2.6!skip
+plugins/module_utils/k8s/client.py compile-2.7!skip
+plugins/module_utils/k8s/client.py compile-3.5!skip
+plugins/module_utils/k8s/runner.py compile-2.6!skip
+plugins/module_utils/k8s/runner.py compile-2.7!skip
+plugins/module_utils/k8s/runner.py compile-3.5!skip
+plugins/module_utils/k8s/service.py compile-2.6!skip
+plugins/module_utils/k8s/service.py compile-2.7!skip
+plugins/module_utils/k8s/service.py compile-3.5!skip
+plugins/module_utils/k8s/exceptions.py compile-2.6!skip
+plugins/module_utils/k8s/exceptions.py compile-2.7!skip
+plugins/module_utils/k8s/exceptions.py compile-3.5!skip
+plugins/connection/kubectl.py compile-2.6!skip
+plugins/connection/kubectl.py compile-2.7!skip
+plugins/connection/kubectl.py compile-3.5!skip
+plugins/inventory/k8s.py compile-2.6!skip
+plugins/inventory/k8s.py compile-2.7!skip
+plugins/inventory/k8s.py compile-3.5!skip
+plugins/lookup/k8s.py compile-2.6!skip
+plugins/lookup/k8s.py compile-2.7!skip
+plugins/lookup/k8s.py compile-3.5!skip
+plugins/lookup/kustomize.py compile-2.6!skip
+plugins/lookup/kustomize.py compile-2.7!skip
+plugins/lookup/kustomize.py compile-3.5!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/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/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/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_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_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_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/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_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_drain.py compile-2.6!skip
+plugins/modules/k8s_drain.py compile-2.7!skip
+plugins/modules/k8s_drain.py compile-3.5!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/k8s_taint.py compile-2.6!skip
+plugins/modules/k8s_taint.py compile-2.7!skip
+plugins/modules/k8s_taint.py compile-3.5!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_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_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_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_cp.py compile-2.6!skip
+plugins/modules/k8s_cp.py compile-2.7!skip
+plugins/modules/k8s_cp.py compile-3.5!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/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/action/k8s_info.py compile-2.6!skip
+plugins/action/k8s_info.py compile-2.7!skip
+plugins/action/k8s_info.py compile-3.5!skip
+plugins/filter/k8s.py compile-2.6!skip
+plugins/filter/k8s.py compile-2.7!skip
+plugins/filter/k8s.py compile-3.5!skip
+tests/unit/conftest.py compile-2.6!skip
+tests/unit/conftest.py compile-2.7!skip
+tests/unit/conftest.py compile-3.5!skip
+tests/unit/utils/ansible_module_mock.py compile-2.6!skip
+tests/unit/utils/ansible_module_mock.py compile-2.7!skip
+tests/unit/utils/ansible_module_mock.py compile-3.5!skip
+tests/unit/module_utils/test_helm.py compile-2.6!skip
+tests/unit/module_utils/test_helm.py compile-2.7!skip
+tests/unit/module_utils/test_helm.py compile-3.5!skip
+tests/unit/module_utils/test_marshal.py compile-2.6!skip
+tests/unit/module_utils/test_marshal.py compile-2.7!skip
+tests/unit/module_utils/test_marshal.py compile-3.5!skip
+tests/unit/module_utils/test_discoverer.py compile-2.6!skip
+tests/unit/module_utils/test_discoverer.py compile-2.7!skip
+tests/unit/module_utils/test_discoverer.py compile-3.5!skip
+tests/unit/module_utils/test_hashes.py compile-2.6!skip
+tests/unit/module_utils/test_hashes.py compile-2.7!skip
+tests/unit/module_utils/test_hashes.py compile-3.5!skip
+tests/unit/module_utils/test_resource.py compile-2.6!skip
+tests/unit/module_utils/test_resource.py compile-2.7!skip
+tests/unit/module_utils/test_resource.py compile-3.5!skip
+tests/unit/module_utils/test_service.py compile-2.6!skip
+tests/unit/module_utils/test_service.py compile-2.7!skip
+tests/unit/module_utils/test_service.py compile-3.5!skip
+tests/unit/module_utils/test_waiter.py compile-2.6!skip
+tests/unit/module_utils/test_waiter.py compile-2.7!skip
+tests/unit/module_utils/test_waiter.py compile-3.5!skip
+tests/unit/module_utils/test_common.py compile-2.6!skip
+tests/unit/module_utils/test_common.py compile-2.7!skip
+tests/unit/module_utils/test_common.py compile-3.5!skip
+tests/unit/module_utils/test_selector.py compile-2.6!skip
+tests/unit/module_utils/test_selector.py compile-2.7!skip
+tests/unit/module_utils/test_selector.py compile-3.5!skip
+tests/unit/module_utils/test_apply.py compile-2.6!skip
+tests/unit/module_utils/test_apply.py compile-2.7!skip
+tests/unit/module_utils/test_apply.py compile-3.5!skip
+tests/unit/module_utils/test_runner.py compile-2.6!skip
+tests/unit/module_utils/test_runner.py compile-2.7!skip
+tests/unit/module_utils/test_runner.py compile-3.5!skip
+tests/unit/module_utils/test_client.py compile-2.6!skip
+tests/unit/module_utils/test_client.py compile-2.7!skip
+tests/unit/module_utils/test_client.py compile-3.5!skip
+tests/unit/module_utils/test_core.py compile-2.6!skip
+tests/unit/module_utils/test_core.py compile-2.7!skip
+tests/unit/module_utils/test_core.py compile-3.5!skip
+tests/unit/modules/test_helm_template_module.py compile-2.6!skip
+tests/unit/modules/test_helm_template_module.py compile-2.7!skip
+tests/unit/modules/test_helm_template_module.py compile-3.5!skip
+tests/unit/modules/test_helm_template.py compile-2.6!skip
+tests/unit/modules/test_helm_template.py compile-2.7!skip
+tests/unit/modules/test_helm_template.py compile-3.5!skip
+tests/unit/modules/test_module_helm.py compile-2.6!skip
+tests/unit/modules/test_module_helm.py compile-2.7!skip
+tests/unit/modules/test_module_helm.py compile-3.5!skip
+tests/unit/action/test_remove_omit.py compile-2.6!skip
+tests/unit/action/test_remove_omit.py compile-2.7!skip
+tests/unit/action/test_remove_omit.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py compile-3.5!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.6!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-2.7!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py compile-3.5!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.6!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-2.7!skip
+tests/integration/targets/helm/library/helm_test_version.py compile-3.5!skip
+plugins/modules/k8s_scale.py pylint!skip
+plugins/modules/helm_template.py pylint!skip
+plugins/modules/k8s_exec.py pylint!skip
+plugins/modules/helm.py pylint!skip
+plugins/modules/helm_plugin_info.py pylint!skip
+plugins/modules/helm_info.py pylint!skip
+plugins/modules/helm_repository.py pylint!skip
+plugins/modules/k8s_rollback.py pylint!skip
+plugins/modules/k8s_log.py pylint!skip
+plugins/modules/k8s_drain.py pylint!skip
+plugins/modules/helm_plugin.py pylint!skip
+plugins/modules/k8s_taint.py pylint!skip
+plugins/modules/k8s.py pylint!skip
+plugins/modules/k8s_service.py pylint!skip
+plugins/modules/k8s_cluster_info.py pylint!skip
+plugins/modules/k8s_info.py pylint!skip
+plugins/modules/k8s_cp.py pylint!skip
+plugins/modules/__init__.py pylint!skip
+plugins/modules/k8s_json_patch.py pylint!skip
+plugins/module_utils/helm.py pylint!skip
+plugins/module_utils/apply.py pylint!skip
+plugins/module_utils/hashes.py pylint!skip
+plugins/module_utils/helm_args_common.py pylint!skip
+plugins/module_utils/version.py pylint!skip
+plugins/module_utils/_version.py pylint!skip
+plugins/module_utils/copy.py pylint!skip
+plugins/module_utils/args_common.py pylint!skip
+plugins/module_utils/__init__.py pylint!skip
+plugins/module_utils/selector.py pylint!skip
+plugins/module_utils/k8sdynamicclient.py pylint!skip
+plugins/module_utils/common.py pylint!skip
+plugins/module_utils/ansiblemodule.py pylint!skip
+plugins/module_utils/exceptions.py pylint!skip
+plugins/module_utils/client/resource.py pylint!skip
+plugins/module_utils/client/discovery.py pylint!skip
+plugins/module_utils/k8s/resource.py pylint!skip
+plugins/module_utils/k8s/core.py pylint!skip
+plugins/module_utils/k8s/waiter.py pylint!skip
+plugins/module_utils/k8s/client.py pylint!skip
+plugins/module_utils/k8s/runner.py pylint!skip
+plugins/module_utils/k8s/service.py pylint!skip
+plugins/module_utils/k8s/exceptions.py pylint!skip
+tests/integration/targets/k8s_copy/library/k8s_create_file.py pylint!skip
+tests/integration/targets/k8s_copy/library/kubectl_file_compare.py pylint!skip
+tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py pylint!skip
+tests/integration/targets/helm/library/helm_test_version.py pylint!skip
+tests/unit/conftest.py pylint!skip
+tests/unit/utils/ansible_module_mock.py pylint!skip
+tests/unit/module_utils/test_helm.py pylint!skip
+tests/unit/module_utils/test_marshal.py pylint!skip
+tests/unit/module_utils/test_discoverer.py pylint!skip
+tests/unit/module_utils/test_hashes.py pylint!skip
+tests/unit/module_utils/test_resource.py pylint!skip
+tests/unit/module_utils/test_service.py pylint!skip
+tests/unit/module_utils/test_waiter.py pylint!skip
+tests/unit/module_utils/test_common.py pylint!skip
+tests/unit/module_utils/test_selector.py pylint!skip
+tests/unit/module_utils/test_apply.py pylint!skip
+tests/unit/module_utils/test_runner.py pylint!skip
+tests/unit/module_utils/test_client.py pylint!skip
+tests/unit/module_utils/test_core.py pylint!skip
+tests/unit/modules/test_helm_template_module.py pylint!skip
+tests/unit/modules/test_helm_template.py pylint!skip
+tests/unit/modules/test_module_helm.py pylint!skip
+tests/unit/action/test_remove_omit.py pylint!skip
+plugins/modules/k8s.py validate-modules!skip
+plugins/modules/k8s_cp.py validate-modules!skip
+plugins/modules/k8s_drain.py validate-modules!skip
+plugins/modules/k8s_exec.py validate-modules!skip
+plugins/modules/k8s_info.py validate-modules!skip
+plugins/modules/k8s_json_patch.py validate-modules!skip
+plugins/modules/k8s_log.py validate-modules!skip
+plugins/modules/k8s_rollback.py validate-modules!skip
+plugins/modules/k8s_scale.py validate-modules!skip
+plugins/modules/k8s_service.py validate-modules!skip
+plugins/modules/k8s_taint.py validate-modules!skip
diff --git a/ansible_collections/kubernetes/core/tests/sanity/refresh_ignore_files b/ansible_collections/kubernetes/core/tests/sanity/refresh_ignore_files
new file mode 100644
index 00000000..bdb01b8c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/sanity/refresh_ignore_files
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+
+
+import itertools
+
+from pathlib import Path
+
+
+# Mapping of Ansible versions to supported Python versions
+ANSIBLE_VERSIONS = {
+ "2.9": ["3.6", "3.7", "3.8"],
+ "2.10": ["3.6", "3.7", "3.8", "3.9"],
+ "2.11": ["3.6", "3.7", "3.8", "3.9"],
+ "2.12": ["3.6", "3.7", "3.8", "3.9", "3.10"],
+ "2.13": ["3.6", "3.7", "3.8", "3.9", "3.10"],
+ "2.14": ["3.6", "3.7", "3.8", "3.9", "3.10"],
+ "2.15": ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"],
+}
+
+IMPORT_SKIPS = [
+ "plugins/module_utils/client/discovery.py",
+ "plugins/module_utils/client/resource.py",
+ "plugins/module_utils/k8sdynamicclient.py",
+]
+
+# Adds validate-modules:parameter-type-not-in-doc
+PARAM_TYPE_SKIPS = [
+ "plugins/modules/k8s.py",
+ "plugins/modules/k8s_scale.py",
+ "plugins/modules/k8s_service.py",
+]
+
+# Adds validate-modules:return-syntax-error
+RETURN_SYNTAX_SKIPS = [
+ "plugins/modules/k8s.py",
+ "plugins/modules/k8s_scale.py",
+ "plugins/modules/k8s_service.py",
+ "plugins/modules/k8s_taint.py",
+]
+
+YAML_LINT_SKIPS = [
+ "tests/unit/module_utils/fixtures/definitions.yml",
+ "tests/unit/module_utils/fixtures/deployments.yml",
+ "tests/unit/module_utils/fixtures/pods.yml",
+ "tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml",
+ "tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml",
+ "tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml",
+ "tests/integration/targets/helm/files/test-chart/templates/configmap.yaml",
+ "tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml",
+ "tests/integration/targets/k8s_scale/files/deployment.yaml",
+]
+
+# Add shebang!skip
+SHEBANG_SKIPS = [
+ "tests/sanity/refresh_ignore_files",
+]
+
+# Add validate-modules:import-error
+VALIDATE_IMPORT_SKIPS = [
+ "plugins/modules/k8s.py",
+ "plugins/modules/k8s_cp.py",
+ "plugins/modules/k8s_drain.py",
+ "plugins/modules/k8s_exec.py",
+ "plugins/modules/k8s_info.py",
+ "plugins/modules/k8s_json_patch.py",
+ "plugins/modules/k8s_log.py",
+ "plugins/modules/k8s_rollback.py",
+ "plugins/modules/k8s_scale.py",
+ "plugins/modules/k8s_service.py",
+ "plugins/modules/k8s_taint.py",
+]
+
+
+def import_skips(*versions):
+ for f in IMPORT_SKIPS:
+ for v in versions:
+ yield f"{f} import-{v}!skip"
+
+def param_type_skips(ansible_version):
+ if ansible_version not in ("2.9", "2.10"):
+ for f in PARAM_TYPE_SKIPS:
+ yield f"{f} validate-modules:parameter-type-not-in-doc"
+
+
+def return_syntax_skips(ansible_version):
+ if ansible_version not in ("2.9", "2.10"):
+ for f in RETURN_SYNTAX_SKIPS:
+ yield f"{f} validate-modules:return-syntax-error"
+ else:
+ yield
+
+
+def yaml_lint_skips():
+ for f in YAML_LINT_SKIPS:
+ yield f"{f} yamllint!skip"
+
+
+def shebang_skips():
+ for f in SHEBANG_SKIPS:
+ yield f"{f} shebang!skip"
+
+
+def import_boilerplate(path, ansible_version):
+ if ansible_version in ("2.9", "2.10", "2.11"):
+ for f in (p for p in path.glob("**/*.py") if not p.is_symlink()):
+ yield f"{f} future-import-boilerplate!skip"
+ else:
+ yield
+
+
+def metaclass_boilerplate(path, ansible_version):
+ if ansible_version in ("2.9", "2.10", "2.11"):
+ for f in (p for p in path.glob("**/*.py") if not p.is_symlink()):
+ yield f"{f} metaclass-boilerplate!skip"
+ else:
+ yield
+
+
+def unsupported_compile_skips(path, ansible_version):
+ """This adds rules for compile skips for all unsupported versions of python.
+
+ These aren't needed for Ansible version 2.12+ as that can be managed on a
+ global level in tests/config.yml.
+ """
+ if ansible_version in ("2.9", "2.10", "2.11"):
+ for f in (p for p in path.glob("**/*.py") if not p.is_symlink()):
+ yield (
+ f"{f} compile-2.6!skip\n"
+ f"{f} compile-2.7!skip\n"
+ f"{f} compile-3.5!skip"
+ )
+
+
+def unsupported_import_skips(path, ansible_version):
+ """This adds rules for import skips for all unsupported versions of python.
+
+ These aren't needed for Ansible version 2.12+ as that can be managed on a
+ global level in tests/config.yml.
+ """
+ if ansible_version in ("2.9", "2.10", "2.11"):
+ if ansible_version in ("2.9", "2.10") and path.name == "plugins":
+ pathglob = itertools.chain(
+ path.joinpath("modules").glob("**/*.py"),
+ path.joinpath("module_utils").glob("**/*.py")
+ )
+ else:
+ pathglob = path.glob("**/*.py")
+ for f in (p for p in pathglob if not p.is_symlink()):
+ yield (
+ f"{f} import-2.6!skip\n"
+ f"{f} import-2.7!skip\n"
+ f"{f} import-3.5!skip"
+ )
+
+
+def unsupported_pylint_skips(path, ansible_version):
+ """This adds rules to skip pylint checks.
+
+ This is only a problem on Ansible version 2.9 and 2.10 with python 3.5,
+ but there's no way to restrict this to a specific version of python.
+ """
+ if ansible_version in ("2.9", "2.10"):
+ pathglob = itertools.chain(
+ path.joinpath("plugins/modules").glob("**/*.py"),
+ path.joinpath("plugins/module_utils").glob("**/*.py"),
+ path.joinpath("tests").glob("**/*.py"),
+ )
+ for f in (p for p in pathglob if not p.is_symlink()):
+ yield f"{f} pylint!skip"
+
+
+def unsupported_validate_modules_skips(ansible_version):
+ """Disable validate-modules test.
+
+ Unfortunately, this is overly broad. Applying a validate-modules:import-error
+ skip fixes ansible 2.9 and python <3.6, but causes validation of the ignores
+ file itself to fail in python 3.6+. The only solution here is to simply
+ skip validate-modules altogether.
+ """
+ if ansible_version in ("2.9", "2.10"):
+ for f in VALIDATE_IMPORT_SKIPS:
+ yield f"{f} validate-modules!skip"
+
+
+def main():
+ target_dir = Path('.')
+ sanity_dir = target_dir / "tests" / "sanity"
+ plugins = target_dir / "plugins"
+ units = target_dir / "tests" / "unit"
+ integration = target_dir / "tests" / "integration"
+
+ for ansible, python in ANSIBLE_VERSIONS.items():
+ with open(sanity_dir / f"ignore-{ansible}.txt", "w") as fp:
+ ignores = itertools.chain(
+ import_skips(*python),
+ param_type_skips(ansible),
+ yaml_lint_skips(),
+ shebang_skips(),
+ return_syntax_skips(ansible),
+ import_boilerplate(plugins, ansible),
+ import_boilerplate(units, ansible),
+ metaclass_boilerplate(plugins, ansible),
+ metaclass_boilerplate(units, ansible),
+ unsupported_import_skips(plugins, ansible),
+ unsupported_compile_skips(plugins, ansible),
+ unsupported_compile_skips(units, ansible),
+ unsupported_compile_skips(integration, ansible),
+ unsupported_pylint_skips(target_dir, ansible),
+ unsupported_validate_modules_skips(ansible),
+ )
+ for f in filter(None, ignores):
+ fp.write(f + "\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/kubernetes/core/tests/unit/action/test_remove_omit.py b/ansible_collections/kubernetes/core/tests/unit/action/test_remove_omit.py
new file mode 100644
index 00000000..3432c19f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/action/test_remove_omit.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2022, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from datetime import datetime
+from ansible_collections.kubernetes.core.plugins.action.k8s_info import RemoveOmit
+
+
+def get_omit_token():
+ return "__omit_place_holder__%s" % datetime.now().strftime("%Y%m%d%H%M%S")
+
+
+def test_remove_omit_from_str():
+ omit_token = get_omit_token()
+ src = """
+ project: ansible
+ collection: {omit}
+ """.format(
+ omit=omit_token
+ )
+ result = RemoveOmit(src, omit_value=omit_token).output()
+ assert len(result) == 1
+ assert result[0] == dict(project="ansible")
+
+
+def test_remove_omit_from_list():
+ omit_token = get_omit_token()
+ src = """
+ items:
+ - {omit}
+ """.format(
+ omit=omit_token
+ )
+ result = RemoveOmit(src, omit_value=omit_token).output()
+ assert len(result) == 1
+ assert result[0] == dict(items=[])
+
+
+def test_remove_omit_from_list_of_dict():
+ omit_token = get_omit_token()
+ src = """
+ items:
+ - owner: ansible
+ team: {omit}
+ - simple_list_item
+ """.format(
+ omit=omit_token
+ )
+ result = RemoveOmit(src, omit_value=omit_token).output()
+ assert len(result) == 1
+ assert result[0] == dict(items=[dict(owner="ansible"), "simple_list_item"])
+
+
+def test_remove_omit_combined():
+ omit_token = get_omit_token()
+ src = """
+ items:
+ - {omit}
+ - list_item_a
+ - list_item_b
+ parent:
+ child:
+ subchilda: {omit}
+ subchildb:
+ name: {omit}
+ age: 3
+ """.format(
+ omit=omit_token
+ )
+ result = RemoveOmit(src, omit_value=omit_token).output()
+ assert len(result) == 1
+ assert result[0] == dict(
+ items=["list_item_a", "list_item_b"],
+ parent=dict(child=dict(subchildb=dict(age=3))),
+ )
+
+
+def test_remove_omit_mutiple_documents():
+ omit_token = get_omit_token()
+ src = [
+ """
+ project: ansible
+ collection: {omit}
+ """.format(
+ omit=omit_token
+ ),
+ "---",
+ """
+ project: kubernetes
+ environment: production
+ collection: {omit}""".format(
+ omit=omit_token
+ ),
+ ]
+ src = "\n".join(src)
+ print(src)
+ result = RemoveOmit(src, omit_value=omit_token).output()
+ assert len(result) == 2
+ assert result[0] == dict(project="ansible")
+ assert result[1] == dict(project="kubernetes", environment="production")
diff --git a/ansible_collections/kubernetes/core/tests/unit/conftest.py b/ansible_collections/kubernetes/core/tests/unit/conftest.py
new file mode 100644
index 00000000..20615adb
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/conftest.py
@@ -0,0 +1,44 @@
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import json
+import sys
+from io import BytesIO
+
+import pytest
+
+import ansible.module_utils.basic
+from ansible.module_utils.six import string_types
+from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common._collections_compat import MutableMapping
+
+
+@pytest.fixture
+def stdin(mocker, request):
+ old_args = ansible.module_utils.basic._ANSIBLE_ARGS
+ ansible.module_utils.basic._ANSIBLE_ARGS = None
+ old_argv = sys.argv
+ sys.argv = ["ansible_unittest"]
+
+ if isinstance(request.param, string_types):
+ args = request.param
+ elif isinstance(request.param, MutableMapping):
+ if "ANSIBLE_MODULE_ARGS" not in request.param:
+ request.param = {"ANSIBLE_MODULE_ARGS": request.param}
+ if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]:
+ request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp"
+ if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]:
+ request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False
+ args = json.dumps(request.param)
+ else:
+ raise Exception("Malformed data to the stdin pytest fixture")
+
+ fake_stdin = BytesIO(to_bytes(args, errors="surrogate_or_strict"))
+ mocker.patch("ansible.module_utils.basic.sys.stdin", mocker.MagicMock())
+ mocker.patch("ansible.module_utils.basic.sys.stdin.buffer", fake_stdin)
+
+ yield fake_stdin
+
+ ansible.module_utils.basic._ANSIBLE_ARGS = old_args
+ sys.argv = old_argv
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/definitions.yml b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/definitions.yml
new file mode 100644
index 00000000..a8f6de81
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/definitions.yml
@@ -0,0 +1,34 @@
+---
+kind: Namespace
+apiVersion: v1
+metadata:
+ name: test-1
+---
+kind: Pod
+apiVersion: v1
+metadata:
+ name: pod-1
+ namespace: test-1
+spec:
+ containers:
+ - image: busybox
+ name: busybox
+---
+kind: PodList
+apiVersion: v1
+metadata: {}
+items:
+ - kind: Pod
+ apiVersion: v1
+ metadata:
+ name: pod-1
+ namespace: test-1
+ spec:
+ containers:
+ - image: busybox
+ name: busybox
+---
+kind: ConfigMapList
+apiVersion: v1
+metadata: {}
+items: []
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/deployments.yml b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/deployments.yml
new file mode 100644
index 00000000..530035fc
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/deployments.yml
@@ -0,0 +1,48 @@
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: deploy-1
+ namespace: test-1
+ generation: 1
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: foo
+ template:
+ metadata:
+ labels:
+ app: foo
+ spec:
+ containers:
+ - image: busybox
+ name: busybox
+status:
+ availableReplicas: 2
+ replicas: 2
+ observedGeneration: 1
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: deploy-2
+ namespace: test-1
+ generation: 1
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: foo
+ template:
+ metadata:
+ labels:
+ app: foo
+ spec:
+ containers:
+ - image: busybox
+ name: busybox
+status:
+ availableReplicas: 1
+ replicas: 2
+ observedGeneration: 1
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/pods.yml b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/pods.yml
new file mode 100644
index 00000000..250354e2
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/fixtures/pods.yml
@@ -0,0 +1,63 @@
+---
+kind: Pod
+apiVersion: v1
+metadata:
+ namespace: test-1
+ name: pod-1
+spec:
+ containers:
+ - image: busybox
+ name: busybox
+status:
+ containerStatuses:
+ - name: busybox
+ ready: true
+ conditions:
+ - type: "www.example.com/gate"
+ status: "True"
+---
+kind: Pod
+apiVersion: v1
+metadata:
+ namespace: test-1
+ name: pod-2
+spec:
+ containers:
+ - image: busybox
+ name: busybox
+---
+kind: Pod
+apiVersion: v1
+metadata:
+ namespace: test-1
+ name: pod-3
+spec:
+ containers:
+ - image: busybox
+ name: busybox
+status:
+ phase: Pending
+ conditions:
+ - type: "www.example.com/gate"
+ status: "Unknown"
+ containerStatuses:
+ - name: busybox
+ ready: true
+---
+kind: Pod
+apiVersion: v1
+metadata:
+ namespace: test-1
+ name: pod-4
+spec:
+ containers:
+ - image: busybox
+ name: busybox
+status:
+ phase: Pending
+ conditions:
+ - type: "www.example.com/other"
+ status: "Unknown"
+ containerStatuses:
+ - name: busybox
+ ready: true
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_apply.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_apply.py
new file mode 100644
index 00000000..07986007
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_apply.py
@@ -0,0 +1,490 @@
+# Copyright [2017] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible_collections.kubernetes.core.plugins.module_utils.apply import (
+ merge,
+ apply_patch,
+)
+
+tests = [
+ dict(
+ last_applied=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", two="2")
+ ),
+ desired=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", two="2")
+ ),
+ expected={},
+ ),
+ dict(
+ last_applied=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", two="2")
+ ),
+ desired=dict(
+ kind="ConfigMap",
+ metadata=dict(name="foo"),
+ data=dict(one="1", two="2", three="3"),
+ ),
+ expected=dict(data=dict(three="3")),
+ ),
+ dict(
+ last_applied=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", two="2")
+ ),
+ desired=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", three="3")
+ ),
+ expected=dict(data=dict(two=None, three="3")),
+ ),
+ dict(
+ last_applied=dict(
+ kind="ConfigMap",
+ metadata=dict(name="foo", annotations=dict(this="one", hello="world")),
+ data=dict(one="1", two="2"),
+ ),
+ desired=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", three="3")
+ ),
+ expected=dict(metadata=dict(annotations=None), data=dict(two=None, three="3")),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, name="http")]),
+ ),
+ actual=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, protocol="TCP", name="http")]),
+ ),
+ desired=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, name="http")]),
+ ),
+ expected=dict(spec=dict(ports=[dict(port=8080, protocol="TCP", name="http")])),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, name="http")]),
+ ),
+ actual=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, protocol="TCP", name="http")]),
+ ),
+ desired=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8081, name="http")]),
+ ),
+ expected=dict(spec=dict(ports=[dict(port=8081, name="http")])),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, name="http")]),
+ ),
+ actual=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, protocol="TCP", name="http")]),
+ ),
+ desired=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(
+ ports=[dict(port=8443, name="https"), dict(port=8080, name="http")]
+ ),
+ ),
+ expected=dict(
+ spec=dict(
+ ports=[
+ dict(port=8443, name="https"),
+ dict(port=8080, name="http", protocol="TCP"),
+ ]
+ )
+ ),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(
+ ports=[dict(port=8443, name="https"), dict(port=8080, name="http")]
+ ),
+ ),
+ actual=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(
+ ports=[
+ dict(port=8443, protocol="TCP", name="https"),
+ dict(port=8080, protocol="TCP", name="http"),
+ ]
+ ),
+ ),
+ desired=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8080, name="http")]),
+ ),
+ expected=dict(spec=dict(ports=[dict(port=8080, name="http", protocol="TCP")])),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(
+ ports=[
+ dict(port=8443, name="https", madeup="xyz"),
+ dict(port=8080, name="http"),
+ ]
+ ),
+ ),
+ actual=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(
+ ports=[
+ dict(port=8443, protocol="TCP", name="https", madeup="xyz"),
+ dict(port=8080, protocol="TCP", name="http"),
+ ]
+ ),
+ ),
+ desired=dict(
+ kind="Service",
+ metadata=dict(name="foo"),
+ spec=dict(ports=[dict(port=8443, name="https")]),
+ ),
+ expected=dict(
+ spec=dict(
+ ports=[dict(madeup=None, port=8443, name="https", protocol="TCP")]
+ )
+ ),
+ ),
+ dict(
+ last_applied=dict(
+ kind="Pod",
+ metadata=dict(name="foo"),
+ spec=dict(
+ containers=[
+ dict(
+ name="busybox",
+ image="busybox",
+ resources=dict(
+ requests=dict(cpu="100m", memory="100Mi"),
+ limits=dict(cpu="100m", memory="100Mi"),
+ ),
+ )
+ ]
+ ),
+ ),
+ actual=dict(
+ kind="Pod",
+ metadata=dict(name="foo"),
+ spec=dict(
+ containers=[
+ dict(
+ name="busybox",
+ image="busybox",
+ resources=dict(
+ requests=dict(cpu="100m", memory="100Mi"),
+ limits=dict(cpu="100m", memory="100Mi"),
+ ),
+ )
+ ]
+ ),
+ ),
+ desired=dict(
+ kind="Pod",
+ metadata=dict(name="foo"),
+ spec=dict(
+ containers=[
+ dict(
+ name="busybox",
+ image="busybox",
+ resources=dict(
+ requests=dict(cpu="50m", memory="50Mi"),
+ limits=dict(memory="50Mi"),
+ ),
+ )
+ ]
+ ),
+ ),
+ expected=dict(
+ spec=dict(
+ containers=[
+ dict(
+ name="busybox",
+ image="busybox",
+ resources=dict(
+ requests=dict(cpu="50m", memory="50Mi"),
+ limits=dict(cpu=None, memory="50Mi"),
+ ),
+ )
+ ]
+ )
+ ),
+ ),
+ dict(
+ desired=dict(
+ kind="Pod",
+ spec=dict(
+ containers=[
+ dict(
+ name="hello",
+ volumeMounts=[dict(name="test", mountPath="/test")],
+ )
+ ],
+ volumes=[dict(name="test", configMap=dict(name="test"))],
+ ),
+ ),
+ last_applied=dict(
+ kind="Pod",
+ spec=dict(
+ containers=[
+ dict(
+ name="hello",
+ volumeMounts=[dict(name="test", mountPath="/test")],
+ )
+ ],
+ volumes=[dict(name="test", configMap=dict(name="test"))],
+ ),
+ ),
+ actual=dict(
+ kind="Pod",
+ spec=dict(
+ containers=[
+ dict(
+ name="hello",
+ volumeMounts=[
+ dict(name="test", mountPath="/test"),
+ dict(
+ mountPath="/var/run/secrets/kubernetes.io/serviceaccount",
+ name="default-token-xyz",
+ ),
+ ],
+ )
+ ],
+ volumes=[
+ dict(name="test", configMap=dict(name="test")),
+ dict(
+ name="default-token-xyz",
+ secret=dict(secretName="default-token-xyz"),
+ ),
+ ],
+ ),
+ ),
+ expected=dict(
+ spec=dict(
+ containers=[
+ dict(
+ name="hello",
+ volumeMounts=[
+ dict(name="test", mountPath="/test"),
+ dict(
+ mountPath="/var/run/secrets/kubernetes.io/serviceaccount",
+ name="default-token-xyz",
+ ),
+ ],
+ )
+ ],
+ volumes=[
+ dict(name="test", configMap=dict(name="test")),
+ dict(
+ name="default-token-xyz",
+ secret=dict(secretName="default-token-xyz"),
+ ),
+ ],
+ )
+ ),
+ ),
+ # This next one is based on a real world case where definition was mostly
+ # str type and everything else was mostly unicode type (don't ask me how)
+ dict(
+ last_applied={
+ "kind": "ConfigMap",
+ "data": {"one": "1", "three": "3", "two": "2"},
+ "apiVersion": "v1",
+ "metadata": {"namespace": "apply", "name": "apply-configmap"},
+ },
+ actual={
+ "kind": "ConfigMap",
+ "data": {"one": "1", "three": "3", "two": "2"},
+ "apiVersion": "v1",
+ "metadata": {
+ "namespace": "apply",
+ "name": "apply-configmap",
+ "resourceVersion": "1714994",
+ "creationTimestamp": "2019-08-17T05:08:05Z",
+ "annotations": {},
+ "selfLink": "/api/v1/namespaces/apply/configmaps/apply-configmap",
+ "uid": "fed45fb0-c0ac-11e9-9d95-025000000001",
+ },
+ },
+ desired={
+ "kind": "ConfigMap",
+ "data": {"one": "1", "three": "3", "two": "2"},
+ "apiVersion": "v1",
+ "metadata": {"namespace": "apply", "name": "apply-configmap"},
+ },
+ expected=dict(),
+ ),
+ # apply a Deployment, then scale the Deployment (which doesn't affect last-applied)
+ # then apply the Deployment again. Should un-scale the Deployment
+ dict(
+ last_applied={
+ "kind": "Deployment",
+ "spec": {
+ "replicas": 1,
+ "template": {
+ "spec": {
+ "containers": [
+ {
+ "name": "this_must_exist",
+ "envFrom": [
+ {"configMapRef": {"name": "config-xyz"}},
+ {"secretRef": {"name": "config-wxy"}},
+ ],
+ }
+ ]
+ }
+ },
+ },
+ "metadata": {"namespace": "apply", "name": "apply-deployment"},
+ },
+ actual={
+ "kind": "Deployment",
+ "spec": {
+ "replicas": 0,
+ "template": {
+ "spec": {
+ "containers": [
+ {
+ "name": "this_must_exist",
+ "envFrom": [
+ {"configMapRef": {"name": "config-xyz"}},
+ {"secretRef": {"name": "config-wxy"}},
+ ],
+ }
+ ]
+ }
+ },
+ },
+ "metadata": {"namespace": "apply", "name": "apply-deployment"},
+ },
+ desired={
+ "kind": "Deployment",
+ "spec": {
+ "replicas": 1,
+ "template": {
+ "spec": {
+ "containers": [
+ {
+ "name": "this_must_exist",
+ "envFrom": [{"configMapRef": {"name": "config-abc"}}],
+ }
+ ]
+ }
+ },
+ },
+ "metadata": {"namespace": "apply", "name": "apply-deployment"},
+ },
+ expected={
+ "spec": {
+ "replicas": 1,
+ "template": {
+ "spec": {
+ "containers": [
+ {
+ "name": "this_must_exist",
+ "envFrom": [{"configMapRef": {"name": "config-abc"}}],
+ }
+ ]
+ }
+ },
+ }
+ },
+ ),
+ dict(
+ last_applied={"kind": "MadeUp", "toplevel": {"original": "entry"}},
+ actual={
+ "kind": "MadeUp",
+ "toplevel": {
+ "original": "entry",
+ "another": {"nested": {"entry": "value"}},
+ },
+ },
+ desired={
+ "kind": "MadeUp",
+ "toplevel": {
+ "original": "entry",
+ "another": {"nested": {"entry": "value"}},
+ },
+ },
+ expected={},
+ ),
+]
+
+
+def test_merges():
+ for test in tests:
+ assert (
+ merge(
+ test["last_applied"],
+ test["desired"],
+ test.get("actual", test["last_applied"]),
+ )
+ == test["expected"]
+ )
+
+
+def test_apply_patch():
+ actual = dict(
+ kind="ConfigMap",
+ metadata=dict(
+ name="foo",
+ annotations={
+ "kubectl.kubernetes.io/last-applied-configuration": '{"data":{"one":"1","two":"2"},"kind":"ConfigMap",'
+ '"metadata":{"annotations":{"hello":"world","this":"one"},"name":"foo"}}',
+ "this": "one",
+ "hello": "world",
+ },
+ ),
+ data=dict(one="1", two="2"),
+ )
+ desired = dict(
+ kind="ConfigMap", metadata=dict(name="foo"), data=dict(one="1", three="3")
+ )
+ expected = dict(
+ metadata=dict(
+ annotations={
+ "kubectl.kubernetes.io/last-applied-configuration": '{"data":{"one":"1","three":"3"},"kind":"ConfigMap","metadata":{"name":"foo"}}',
+ "this": None,
+ "hello": None,
+ }
+ ),
+ data=dict(two=None, three="3"),
+ )
+ assert apply_patch(actual, desired) == (actual, expected)
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_client.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_client.py
new file mode 100644
index 00000000..bba03589
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_client.py
@@ -0,0 +1,184 @@
+import os
+import base64
+import tempfile
+import yaml
+import mock
+from mock import MagicMock
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
+ _create_auth_spec,
+ _create_configuration,
+)
+
+TEST_HOST = "test-host"
+TEST_SSL_HOST = "https://test-host"
+TEST_CLIENT_CERT = "/dev/null"
+TEST_CLIENT_KEY = "/dev/null"
+TEST_CERTIFICATE_AUTH = "/dev/null"
+TEST_DATA = "test-data"
+TEST_BEARER_TOKEN = "Bearer %s" % base64.standard_b64encode(TEST_DATA.encode()).decode()
+TEST_KUBE_CONFIG = {
+ "current-context": "federal-context",
+ "contexts": [
+ {
+ "name": "simple_token",
+ "context": {"cluster": "default", "user": "simple_token"},
+ }
+ ],
+ "clusters": [{"name": "default", "cluster": {"server": TEST_HOST}}],
+ "users": [
+ {
+ "name": "ssl-no_file",
+ "user": {
+ "token": TEST_BEARER_TOKEN,
+ "client-certificate": TEST_CLIENT_CERT,
+ "client-key": TEST_CLIENT_KEY,
+ },
+ }
+ ],
+}
+
+_temp_files = []
+
+
+def _remove_temp_file():
+ for f in _temp_files:
+ try:
+ os.remove(f)
+ except FileNotFoundError:
+ pass
+
+
+def _create_temp_file(content=""):
+ handler, name = tempfile.mkstemp()
+ _temp_files.append(name)
+ os.write(handler, str.encode(content))
+ os.close(handler)
+ return name
+
+
+def test_create_auth_spec_ssl_no_options():
+ module = MagicMock()
+ module.params = {}
+ actual_auth_spec = _create_auth_spec(module)
+
+ assert "proxy_headers" in actual_auth_spec
+
+
+def test_create_auth_spec_ssl_options():
+ ssl_options = {
+ "host": TEST_SSL_HOST,
+ "token": TEST_BEARER_TOKEN,
+ "client_cert": TEST_CLIENT_CERT,
+ "client_key": TEST_CLIENT_KEY,
+ "ca_cert": TEST_CERTIFICATE_AUTH,
+ "validate_certs": True,
+ }
+ expected_auth_spec = {
+ "host": TEST_SSL_HOST,
+ "cert_file": TEST_CLIENT_CERT,
+ "key_file": TEST_CLIENT_KEY,
+ "ssl_ca_cert": TEST_CERTIFICATE_AUTH,
+ "verify_ssl": True,
+ "proxy_headers": {},
+ }
+
+ module = MagicMock()
+ module.params = ssl_options
+ actual_auth_spec = _create_auth_spec(module)
+
+ assert expected_auth_spec.items() <= actual_auth_spec.items()
+
+
+def test_create_auth_spec_ssl_options_no_verify():
+ ssl_options = {
+ "host": TEST_SSL_HOST,
+ "token": TEST_BEARER_TOKEN,
+ "client_cert": TEST_CLIENT_CERT,
+ "client_key": TEST_CLIENT_KEY,
+ "validate_certs": False,
+ }
+
+ expected_auth_spec = {
+ "host": TEST_SSL_HOST,
+ "cert_file": TEST_CLIENT_CERT,
+ "key_file": TEST_CLIENT_KEY,
+ "verify_ssl": False,
+ "proxy_headers": {},
+ }
+
+ module = MagicMock()
+ module.params = ssl_options
+ actual_auth_spec = _create_auth_spec(module)
+
+ assert expected_auth_spec.items() <= actual_auth_spec.items()
+
+
+@mock.patch.dict(os.environ, {"K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH": "foo:bar"})
+@mock.patch.dict(os.environ, {"K8S_AUTH_PROXY_HEADERS_USER_AGENT": "foo/1.0"})
+@mock.patch.dict(os.environ, {"K8S_AUTH_CERT_FILE": TEST_CLIENT_CERT})
+def test_create_auth_spec_ssl_proxy():
+ expected_auth_spec = {
+ "kubeconfig": "~/.kube/customconfig",
+ "verify_ssl": True,
+ "cert_file": TEST_CLIENT_CERT,
+ "proxy_headers": {"proxy_basic_auth": "foo:bar", "user_agent": "foo/1.0"},
+ }
+ module = MagicMock()
+ options = {"validate_certs": True, "kubeconfig": "~/.kube/customconfig"}
+
+ module.params = options
+ actual_auth_spec = _create_auth_spec(module)
+
+ assert expected_auth_spec.items() <= actual_auth_spec.items()
+
+
+def test_load_kube_config_from_file_path():
+ config_file = _create_temp_file(yaml.safe_dump(TEST_KUBE_CONFIG))
+ auth = {"kubeconfig": config_file, "context": "simple_token"}
+ actual_configuration = _create_configuration(auth)
+
+ expected_configuration = {
+ "host": TEST_HOST,
+ "kubeconfig": config_file,
+ "context": "simple_token",
+ }
+
+ assert expected_configuration.items() <= actual_configuration.__dict__.items()
+ _remove_temp_file()
+
+
+def test_load_kube_config_from_dict():
+ auth_spec = {"kubeconfig": TEST_KUBE_CONFIG, "context": "simple_token"}
+ actual_configuration = _create_configuration(auth_spec)
+
+ expected_configuration = {
+ "host": TEST_HOST,
+ "kubeconfig": TEST_KUBE_CONFIG,
+ "context": "simple_token",
+ }
+
+ assert expected_configuration.items() <= actual_configuration.__dict__.items()
+ _remove_temp_file()
+
+
+def test_create_auth_spec_with_aliases_in_kwargs():
+ auth_options = {
+ "host": TEST_HOST,
+ "cert_file": TEST_CLIENT_CERT,
+ "ssl_ca_cert": TEST_CERTIFICATE_AUTH,
+ "key_file": TEST_CLIENT_KEY,
+ "verify_ssl": True,
+ }
+
+ expected_auth_spec = {
+ "host": TEST_HOST,
+ "cert_file": TEST_CLIENT_CERT,
+ "ssl_ca_cert": TEST_CERTIFICATE_AUTH,
+ "key_file": TEST_CLIENT_KEY,
+ "verify_ssl": True,
+ }
+
+ actual_auth_spec = _create_auth_spec(module=None, **auth_options)
+ for key, value in expected_auth_spec.items():
+ assert value == actual_auth_spec.get(key)
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_common.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_common.py
new file mode 100644
index 00000000..f4e5028b
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_common.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+from ansible_collections.kubernetes.core.plugins.module_utils.common import (
+ _encode_stringdata,
+)
+
+
+def test_encode_stringdata_modifies_definition():
+ definition = {
+ "apiVersion": "v1",
+ "kind": "Secret",
+ "type": "Opaque",
+ "stringData": {"mydata": "ansiβle"},
+ }
+ res = _encode_stringdata(definition)
+ assert "stringData" not in res
+ assert res["data"]["mydata"] == "YW5zac6ybGU="
+
+
+def test_encode_stringdata_does_not_modify_data():
+ definition = {
+ "apiVersion": "v1",
+ "kind": "Secret",
+ "type": "Opaque",
+ "data": {"mydata": "Zm9vYmFy"},
+ }
+ res = _encode_stringdata(definition)
+ assert res["data"]["mydata"] == "Zm9vYmFy"
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_core.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_core.py
new file mode 100644
index 00000000..189f1f95
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_core.py
@@ -0,0 +1,91 @@
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import json
+
+import kubernetes
+import pytest
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
+ AnsibleK8SModule,
+)
+
+MINIMAL_K8S_VERSION = "12.0.0"
+UNSUPPORTED_K8S_VERSION = "11.0.0"
+
+
+@pytest.mark.parametrize("stdin", [{}], indirect=["stdin"])
+def test_no_warn(monkeypatch, stdin, capfd):
+ monkeypatch.setattr(kubernetes, "__version__", MINIMAL_K8S_VERSION)
+
+ module = AnsibleK8SModule(argument_spec={})
+ with pytest.raises(SystemExit):
+ module.exit_json()
+ out, err = capfd.readouterr()
+
+ return_value = json.loads(out)
+
+ assert return_value.get("exception") is None
+ assert return_value.get("warnings") is None
+ assert return_value.get("failed") is None
+
+
+@pytest.mark.parametrize("stdin", [{}], indirect=["stdin"])
+def test_warn_on_k8s_version(monkeypatch, stdin, capfd):
+ monkeypatch.setattr(kubernetes, "__version__", UNSUPPORTED_K8S_VERSION)
+
+ module = AnsibleK8SModule(argument_spec={})
+ with pytest.raises(SystemExit):
+ module.exit_json()
+ out, err = capfd.readouterr()
+
+ return_value = json.loads(out)
+
+ assert return_value.get("warnings") is not None
+ warnings = return_value["warnings"]
+ assert len(warnings) == 1
+ assert "kubernetes" in warnings[0]
+ assert MINIMAL_K8S_VERSION in warnings[0]
+
+
+dependencies = [
+ ["18.20.0", "12.0.1", False],
+ ["18.20.0", "18.20.0", True],
+ ["12.0.1", "18.20.0", True],
+]
+
+
+@pytest.mark.parametrize(
+ "stdin,desired,actual,result", [({}, *d) for d in dependencies], indirect=["stdin"]
+)
+def test_has_at_least(monkeypatch, stdin, desired, actual, result, capfd):
+ monkeypatch.setattr(kubernetes, "__version__", actual)
+
+ module = AnsibleK8SModule(argument_spec={})
+
+ assert module.has_at_least("kubernetes", desired) is result
+
+
+dependencies = [
+ ["kubernetes", "18.20.0", "(kubernetes>=18.20.0)"],
+ ["foobar", "1.0.0", "(foobar>=1.0.0)"],
+ ["foobar", None, "(foobar)"],
+]
+
+
+@pytest.mark.parametrize(
+ "stdin,dependency,version,msg", [({}, *d) for d in dependencies], indirect=["stdin"]
+)
+def test_requires_fails_with_message(
+ monkeypatch, stdin, dependency, version, msg, capfd
+):
+ monkeypatch.setattr(kubernetes, "__version__", "12.0.0")
+ module = AnsibleK8SModule(argument_spec={})
+ with pytest.raises(SystemExit):
+ module.requires(dependency, version)
+ out, err = capfd.readouterr()
+ return_value = json.loads(out)
+
+ assert return_value.get("failed")
+ assert msg in return_value.get("msg")
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_discoverer.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_discoverer.py
new file mode 100644
index 00000000..b23a7a9a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_discoverer.py
@@ -0,0 +1,165 @@
+# Copyright [2017] [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 pytest
+
+from kubernetes.client import ApiClient
+from kubernetes.dynamic import Resource
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8sdynamicclient import (
+ K8SDynamicClient,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import (
+ LazyDiscoverer,
+)
+from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import (
+ ResourceList,
+)
+
+
+@pytest.fixture(scope="module")
+def mock_namespace():
+ return Resource(
+ api_version="v1",
+ kind="Namespace",
+ name="namespaces",
+ namespaced=False,
+ preferred=True,
+ prefix="api",
+ shorter_names=["ns"],
+ shortNames=["ns"],
+ singularName="namespace",
+ verbs=["create", "delete", "get", "list", "patch", "update", "watch"],
+ )
+
+
+@pytest.fixture(scope="module")
+def mock_templates():
+ return Resource(
+ api_version="v1",
+ kind="Template",
+ name="templates",
+ namespaced=True,
+ preferred=True,
+ prefix="api",
+ shorter_names=[],
+ shortNames=[],
+ verbs=["create", "delete", "get", "list", "patch", "update", "watch"],
+ )
+
+
+@pytest.fixture(scope="module")
+def mock_processedtemplates():
+ return Resource(
+ api_version="v1",
+ kind="Template",
+ name="processedtemplates",
+ namespaced=True,
+ preferred=True,
+ prefix="api",
+ shorter_names=[],
+ shortNames=[],
+ verbs=["create", "delete", "get", "list", "patch", "update", "watch"],
+ )
+
+
+@pytest.fixture(scope="module")
+def mock_namespace_list(mock_namespace):
+ ret = ResourceList(
+ mock_namespace.client,
+ mock_namespace.group,
+ mock_namespace.api_version,
+ mock_namespace.kind,
+ )
+ ret._ResourceList__base_resource = mock_namespace
+ return ret
+
+
+@pytest.fixture(scope="function", autouse=True)
+def setup_client_monkeypatch(
+ monkeypatch,
+ mock_namespace,
+ mock_namespace_list,
+ mock_templates,
+ mock_processedtemplates,
+):
+ def mock_load_server_info(self):
+ self.__version = {"kubernetes": "mock-k8s-version"}
+
+ def mock_parse_api_groups(self, request_resources=False):
+ return {
+ "api": {
+ "": {
+ "v1": {
+ "Namespace": [mock_namespace],
+ "NamespaceList": [mock_namespace_list],
+ "Template": [mock_templates, mock_processedtemplates],
+ }
+ }
+ }
+ }
+
+ monkeypatch.setattr(LazyDiscoverer, "_load_server_info", mock_load_server_info)
+ monkeypatch.setattr(LazyDiscoverer, "parse_api_groups", mock_parse_api_groups)
+
+
+@pytest.fixture
+def client(request):
+ return K8SDynamicClient(ApiClient(), discoverer=LazyDiscoverer)
+
+
+@pytest.mark.parametrize(
+ ("attribute", "value"),
+ [("name", "namespaces"), ("singular_name", "namespace"), ("short_names", ["ns"])],
+)
+def test_search_returns_single_and_list(
+ client, mock_namespace, mock_namespace_list, attribute, value
+):
+ resources = client.resources.search(**{"api_version": "v1", attribute: value})
+
+ assert len(resources) == 2
+ assert mock_namespace in resources
+ assert mock_namespace_list in resources
+
+
+@pytest.mark.parametrize(
+ ("attribute", "value"),
+ [
+ ("kind", "Namespace"),
+ ("name", "namespaces"),
+ ("singular_name", "namespace"),
+ ("short_names", ["ns"]),
+ ],
+)
+def test_get_returns_only_single(client, mock_namespace, attribute, value):
+ resource = client.resources.get(**{"api_version": "v1", attribute: value})
+
+ assert resource == mock_namespace
+
+
+def test_get_namespace_list_kind(client, mock_namespace_list):
+ resource = client.resources.get(api_version="v1", kind="NamespaceList")
+
+ assert resource == mock_namespace_list
+
+
+def test_search_multiple_resources_for_template(
+ client, mock_templates, mock_processedtemplates
+):
+ resources = client.resources.search(api_version="v1", kind="Template")
+
+ assert len(resources) == 2
+ assert mock_templates in resources
+ assert mock_processedtemplates in resources
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_hashes.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_hashes.py
new file mode 100644
index 00000000..f338cb0c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_hashes.py
@@ -0,0 +1,68 @@
+# Copyright [2017] [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.
+
+# Test ConfigMapHash and SecretHash equivalents
+# tests based on https://github.com/kubernetes/kubernetes/pull/49961
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
+ generate_hash,
+)
+
+tests = [
+ dict(
+ resource=dict(kind="ConfigMap", metadata=dict(name="foo"), data=dict()),
+ expected="867km9574f",
+ ),
+ dict(
+ resource=dict(
+ kind="ConfigMap", metadata=dict(name="foo"), type="my-type", data=dict()
+ ),
+ expected="867km9574f",
+ ),
+ dict(
+ resource=dict(
+ kind="ConfigMap",
+ metadata=dict(name="foo"),
+ data=dict(key1="value1", key2="value2"),
+ ),
+ expected="gcb75dd9gb",
+ ),
+ dict(
+ resource=dict(kind="Secret", metadata=dict(name="foo"), data=dict()),
+ expected="949tdgdkgg",
+ ),
+ dict(
+ resource=dict(
+ kind="Secret", metadata=dict(name="foo"), type="my-type", data=dict()
+ ),
+ expected="dg474f9t76",
+ ),
+ dict(
+ resource=dict(
+ kind="Secret",
+ metadata=dict(name="foo"),
+ data=dict(key1="dmFsdWUx", key2="dmFsdWUy"),
+ ),
+ expected="tf72c228m4",
+ ),
+]
+
+
+def test_hashes():
+ for test in tests:
+ assert generate_hash(test["resource"]) == test["expected"]
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_helm.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_helm.py
new file mode 100644
index 00000000..de2c1569
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_helm.py
@@ -0,0 +1,454 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import os.path
+import yaml
+import tempfile
+import pytest
+
+
+from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
+ AnsibleHelmModule,
+ write_temp_kubeconfig,
+)
+from unittest.mock import MagicMock
+import random
+import string
+
+
+@pytest.fixture()
+def ansible_helm_module():
+ module = MagicMock()
+ module.params = {
+ "api_key": None,
+ "ca_cert": None,
+ "host": None,
+ "kube_context": None,
+ "kubeconfig": None,
+ "release_namespace": None,
+ "validate_certs": None,
+ }
+ module.fail_json = MagicMock()
+ module.fail_json.side_effect = SystemExit(1)
+ module.run_command = MagicMock()
+
+ helm_module = AnsibleHelmModule(module=module)
+ helm_module.get_helm_binary = MagicMock()
+ helm_module.get_helm_binary.return_value = "some/path/to/helm/executable"
+
+ return helm_module
+
+
+def test_write_temp_kubeconfig_server_only():
+ content = write_temp_kubeconfig("ff")
+
+ assert content == {
+ "apiVersion": "v1",
+ "clusters": [{"cluster": {"server": "ff"}, "name": "generated-cluster"}],
+ "contexts": [
+ {"context": {"cluster": "generated-cluster"}, "name": "generated-context"}
+ ],
+ "current-context": "generated-context",
+ "kind": "Config",
+ }
+
+
+def test_write_temp_kubeconfig_server_inscure_certs():
+ content = write_temp_kubeconfig("ff", False, "my-certificate")
+
+ assert content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] is True
+ assert (
+ content["clusters"][0]["cluster"]["certificate-authority"] == "my-certificate"
+ )
+
+
+def test_write_temp_kubeconfig_with_kubeconfig():
+ kubeconfig = {
+ "apiVersion": "v1",
+ "kind": "Config",
+ "clusters": [
+ {"cluster": {"server": "myfirstserver"}, "name": "cluster-01"},
+ {"cluster": {"server": "mysecondserver"}, "name": "cluster-02"},
+ ],
+ "contexts": [{"context": {"cluster": "cluster-01"}, "name": "test-context"}],
+ "current-context": "test-context",
+ }
+ content = write_temp_kubeconfig(
+ server="mythirdserver",
+ validate_certs=False,
+ ca_cert="some-ca-cert-for-test",
+ kubeconfig=kubeconfig,
+ )
+
+ expected = {
+ "apiVersion": "v1",
+ "kind": "Config",
+ "clusters": [
+ {
+ "cluster": {
+ "server": "mythirdserver",
+ "insecure-skip-tls-verify": True,
+ "certificate-authority": "some-ca-cert-for-test",
+ },
+ "name": "cluster-01",
+ },
+ {
+ "cluster": {
+ "server": "mythirdserver",
+ "insecure-skip-tls-verify": True,
+ "certificate-authority": "some-ca-cert-for-test",
+ },
+ "name": "cluster-02",
+ },
+ ],
+ "contexts": [{"context": {"cluster": "cluster-01"}, "name": "test-context"}],
+ "current-context": "test-context",
+ }
+
+ assert content == expected
+
+
+def test_module_get_helm_binary_from_params():
+
+ helm_binary_path = MagicMock()
+ helm_sys_binary_path = MagicMock()
+
+ module = MagicMock()
+ module.params = {
+ "binary_path": helm_binary_path,
+ }
+ module.get_bin_path.return_value = helm_sys_binary_path
+
+ helm_module = AnsibleHelmModule(module=module)
+ assert helm_module.get_helm_binary() == helm_binary_path
+
+
+def test_module_get_helm_binary_from_system():
+
+ helm_sys_binary_path = MagicMock()
+ module = MagicMock()
+ module.params = {}
+ module.get_bin_path.return_value = helm_sys_binary_path
+
+ helm_module = AnsibleHelmModule(module=module)
+ assert helm_module.get_helm_binary() == helm_sys_binary_path
+
+
+def test_module_get_helm_plugin_list(ansible_helm_module):
+
+ ansible_helm_module.run_helm_command = MagicMock()
+ ansible_helm_module.run_helm_command.return_value = (0, "output", "error")
+
+ rc, out, err, command = ansible_helm_module.get_helm_plugin_list()
+
+ assert (rc, out, err) == (0, "output", "error")
+ assert command == "some/path/to/helm/executable plugin list"
+
+ ansible_helm_module.get_helm_binary.assert_called_once()
+ ansible_helm_module.run_helm_command.assert_called_once_with(
+ "some/path/to/helm/executable plugin list"
+ )
+
+
+def test_module_get_helm_plugin_list_failure(ansible_helm_module):
+
+ ansible_helm_module.run_helm_command = MagicMock()
+ ansible_helm_module.run_helm_command.return_value = (-1, "output", "error")
+
+ with pytest.raises(SystemExit):
+ ansible_helm_module.get_helm_plugin_list()
+
+ ansible_helm_module.fail_json.assert_called_once_with(
+ msg="Failed to get Helm plugin info",
+ command="some/path/to/helm/executable plugin list",
+ stdout="output",
+ stderr="error",
+ rc=-1,
+ )
+
+
+@pytest.mark.parametrize("no_values", [True, False])
+@pytest.mark.parametrize("get_all", [True, False])
+def test_module_get_values(ansible_helm_module, no_values, get_all):
+
+ expected = {"test": "units"}
+ output = "---\ntest: units\n"
+
+ if no_values:
+ expected = {}
+ output = "null"
+
+ ansible_helm_module.run_helm_command = MagicMock()
+ ansible_helm_module.run_helm_command.return_value = (0, output, "error")
+
+ release_name = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+ result = ansible_helm_module.get_values(release_name, get_all=get_all)
+
+ ansible_helm_module.get_helm_binary.assert_called_once()
+ command = f"some/path/to/helm/executable get values --output=yaml {release_name}"
+ if get_all:
+ command += " -a"
+ ansible_helm_module.run_helm_command.assert_called_once_with(command)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "output,expected",
+ [
+ (
+ 'version.BuildInfo{Version:"v3.10.3", GitCommit:7870ab3ed4135f136eec, GoVersion:"go1.18.9"}',
+ "3.10.3",
+ ),
+ ('Client: &version.Version{SemVer:"v3.12.3", ', "3.12.3"),
+ ('Client: &version.Version{SemVer:"v3.12.3"', None),
+ ],
+)
+def test_module_get_helm_version(ansible_helm_module, output, expected):
+
+ ansible_helm_module.run_command = MagicMock()
+ ansible_helm_module.run_command.return_value = (0, output, "error")
+
+ result = ansible_helm_module.get_helm_version()
+
+ ansible_helm_module.get_helm_binary.assert_called_once()
+ command = "some/path/to/helm/executable version"
+ ansible_helm_module.run_command.assert_called_once_with(command)
+ assert result == expected
+
+
+def test_module_run_helm_command(ansible_helm_module):
+
+ error = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+ output = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+
+ ansible_helm_module.run_command.return_value = (0, output, error)
+
+ ansible_helm_module._prepare_helm_environment = MagicMock()
+ env_update = {x: random.choice(string.ascii_letters) for x in range(10)}
+ ansible_helm_module._prepare_helm_environment.return_value = env_update
+
+ command = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+ rc, out, err = ansible_helm_module.run_helm_command(command)
+
+ assert (rc, out, err) == (0, output, error)
+
+ ansible_helm_module.run_command.assert_called_once_with(
+ command, environ_update=env_update
+ )
+
+
+@pytest.mark.parametrize("fails_on_error", [True, False])
+def test_module_run_helm_command_failure(ansible_helm_module, fails_on_error):
+
+ error = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+ output = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+ return_code = random.randint(1, 10)
+ ansible_helm_module.run_command.return_value = (return_code, output, error)
+
+ ansible_helm_module._prepare_environment = MagicMock()
+
+ command = "".join(
+ random.choice(string.ascii_letters + string.digits) for x in range(10)
+ )
+
+ if fails_on_error:
+ with pytest.raises(SystemExit):
+ rc, out, err = ansible_helm_module.run_helm_command(
+ command, fails_on_error=fails_on_error
+ )
+ ansible_helm_module.fail_json.assert_called_with(
+ msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
+ return_code, output, error
+ ),
+ stdout=output,
+ stderr=error,
+ command=command,
+ )
+ else:
+ rc, out, err = ansible_helm_module.run_helm_command(
+ command, fails_on_error=fails_on_error
+ )
+ assert (rc, out, err) == (return_code, output, error)
+
+
+@pytest.mark.parametrize(
+ "params,env_update,kubeconfig",
+ [
+ (
+ {
+ "api_key": "my-api-key",
+ "host": "some-host",
+ "context": "my-context",
+ "release_namespace": "a-release-namespace",
+ },
+ {
+ "HELM_KUBEAPISERVER": "some-host",
+ "HELM_KUBECONTEXT": "my-context",
+ "HELM_KUBETOKEN": "my-api-key",
+ "HELM_NAMESPACE": "a-release-namespace",
+ },
+ False,
+ ),
+ ({"kubeconfig": {"kube": "config"}}, {}, True),
+ ({"kubeconfig": "path_to_a_config_file"}, {}, True),
+ ],
+)
+def test_module_prepare_helm_environment(params, env_update, kubeconfig):
+
+ module = MagicMock()
+ module.params = params
+
+ helm_module = AnsibleHelmModule(module=module)
+
+ p_kubeconfig = params.get("kubeconfig")
+ tmpfile_name = None
+ if isinstance(p_kubeconfig, str):
+ _fd, tmpfile_name = tempfile.mkstemp()
+ with os.fdopen(_fd, "w") as fp:
+ yaml.dump({"some_custom": "kube_config"}, fp)
+ params["kubeconfig"] = tmpfile_name
+
+ result = helm_module._prepare_helm_environment()
+
+ kubeconfig_path = result.pop("KUBECONFIG", None)
+
+ assert env_update == result
+
+ if kubeconfig:
+ assert os.path.exists(kubeconfig_path)
+ if not tmpfile_name:
+ module.add_cleanup_file.assert_called_with(kubeconfig_path)
+ else:
+ assert kubeconfig_path is None
+
+ if tmpfile_name:
+ os.remove(tmpfile_name)
+
+
+@pytest.mark.parametrize(
+ "helm_version, is_env_var_set",
+ [
+ ("3.10.1", True),
+ ("3.10.0", True),
+ ("3.5.0", False),
+ ("3.8.0", False),
+ ("3.9.35", False),
+ ],
+)
+def test_module_prepare_helm_environment_with_validate_certs(
+ helm_version, is_env_var_set
+):
+
+ module = MagicMock()
+ module.params = {"validate_certs": False}
+
+ helm_module = AnsibleHelmModule(module=module)
+ helm_module.get_helm_version = MagicMock()
+ helm_module.get_helm_version.return_value = helm_version
+
+ result = helm_module._prepare_helm_environment()
+
+ if is_env_var_set:
+ assert result == {"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true"}
+ else:
+ assert list(result.keys()) == ["KUBECONFIG"]
+ kubeconfig_path = result["KUBECONFIG"]
+ assert os.path.exists(kubeconfig_path)
+
+ with open(kubeconfig_path) as fd:
+ content = yaml.safe_load(fd)
+ assert content["clusters"][0]["cluster"]["insecure-skip-tls-verify"] is True
+ os.remove(kubeconfig_path)
+
+
+@pytest.mark.parametrize(
+ "helm_version, is_env_var_set",
+ [
+ ("3.10.0", True),
+ ("3.5.0", True),
+ ("3.4.9", False),
+ ],
+)
+def test_module_prepare_helm_environment_with_ca_cert(helm_version, is_env_var_set):
+
+ ca_cert = "".join(
+ random.choice(string.ascii_letters + string.digits) for i in range(50)
+ )
+ module = MagicMock()
+ module.params = {"ca_cert": ca_cert}
+
+ helm_module = AnsibleHelmModule(module=module)
+ helm_module.get_helm_version = MagicMock()
+ helm_module.get_helm_version.return_value = helm_version
+
+ result = helm_module._prepare_helm_environment()
+
+ if is_env_var_set:
+ assert list(result.keys()) == ["HELM_KUBECAFILE"]
+ assert result["HELM_KUBECAFILE"] == ca_cert
+ else:
+ assert list(result.keys()) == ["KUBECONFIG"]
+ kubeconfig_path = result["KUBECONFIG"]
+ assert os.path.exists(kubeconfig_path)
+
+ with open(kubeconfig_path) as fd:
+ content = yaml.safe_load(fd)
+ import json
+
+ print(json.dumps(content, indent=2))
+ assert content["clusters"][0]["cluster"]["certificate-authority"] == ca_cert
+ os.remove(kubeconfig_path)
+
+
+@pytest.mark.parametrize(
+ "set_values, expected",
+ [
+ ([{"value": "test"}], ["--set test"]),
+ ([{"value_type": "raw", "value": "test"}], ["--set test"]),
+ (
+ [{"value_type": "string", "value": "string_value"}],
+ ["--set-string 'string_value'"],
+ ),
+ ([{"value_type": "file", "value": "file_path"}], ["--set-file 'file_path'"]),
+ (
+ [{"value_type": "json", "value": '{"a": 1, "b": "some_value"}'}],
+ ['--set-json \'{"a": 1, "b": "some_value"}\''],
+ ),
+ (
+ [
+ {"value_type": "string", "value": "string_value"},
+ {"value_type": "file", "value": "file_path"},
+ ],
+ ["--set-string 'string_value'", "--set-file 'file_path'"],
+ ),
+ ],
+)
+def test_module_get_helm_set_values_args(set_values, expected):
+
+ module = MagicMock()
+ module.params = {}
+ module.fail_json.side_effect = SystemExit(1)
+
+ helm_module = AnsibleHelmModule(module=module)
+ helm_module.get_helm_version = MagicMock()
+ helm_module.get_helm_version.return_value = "3.10.1"
+
+ result = helm_module.get_helm_set_values_args(set_values)
+ assert " ".join(expected) == result
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_marshal.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_marshal.py
new file mode 100644
index 00000000..45518029
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_marshal.py
@@ -0,0 +1,97 @@
+# Copyright [2017] [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.
+
+# Test ConfigMap and Secret marshalling
+# tests based on https://github.com/kubernetes/kubernetes/pull/49961
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
+ marshal,
+ sorted_dict,
+)
+
+tests = [
+ dict(
+ resource=dict(
+ kind="ConfigMap",
+ name="",
+ data=dict(),
+ ),
+ expected=b'{"data":{},"kind":"ConfigMap","name":""}',
+ ),
+ dict(
+ resource=dict(
+ kind="ConfigMap",
+ name="",
+ data=dict(one=""),
+ ),
+ expected=b'{"data":{"one":""},"kind":"ConfigMap","name":""}',
+ ),
+ dict(
+ resource=dict(
+ kind="ConfigMap",
+ name="",
+ data=dict(
+ two="2",
+ one="",
+ three="3",
+ ),
+ ),
+ expected=b'{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}',
+ ),
+ dict(
+ resource=dict(
+ kind="Secret",
+ type="my-type",
+ name="",
+ data=dict(),
+ ),
+ expected=b'{"data":{},"kind":"Secret","name":"","type":"my-type"}',
+ ),
+ dict(
+ resource=dict(
+ kind="Secret",
+ type="my-type",
+ name="",
+ data=dict(one=""),
+ ),
+ expected=b'{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}',
+ ),
+ dict(
+ resource=dict(
+ kind="Secret",
+ type="my-type",
+ name="",
+ data=dict(
+ two="Mg==",
+ one="",
+ three="Mw==",
+ ),
+ ),
+ expected=b'{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}',
+ ),
+]
+
+
+def test_marshal():
+ for test in tests:
+ assert (
+ marshal(
+ sorted_dict(test["resource"]), sorted(list(test["resource"].keys()))
+ )
+ == test["expected"]
+ )
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_resource.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_resource.py
new file mode 100644
index 00000000..c27c01a8
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_resource.py
@@ -0,0 +1,187 @@
+import os
+from pathlib import Path
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import (
+ create_definitions,
+ flatten_list_kind,
+ from_file,
+ from_yaml,
+ merge_params,
+)
+
+
+def test_create_definitions_loads_from_definition():
+ params = {
+ "resource_definition": {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "foo", "namespace": "bar"},
+ }
+ }
+ results = create_definitions(params)
+ assert len(results) == 1
+ assert results[0].kind == "Pod"
+ assert results[0].api_version == "v1"
+ assert results[0].name == "foo"
+ assert results[0].namespace == "bar"
+
+
+def test_create_definitions_loads_from_file():
+ current = Path(os.path.dirname(os.path.abspath(__file__)))
+ params = {"src": current / "fixtures/definitions.yml"}
+ results = create_definitions(params)
+ assert len(results) == 3
+ assert results[0].kind == "Namespace"
+ assert results[1].kind == "Pod"
+
+
+def test_create_definitions_loads_from_params():
+ params = {
+ "kind": "Pod",
+ "api_version": "v1",
+ "name": "foo",
+ "namespace": "foobar",
+ }
+ results = create_definitions(params)
+ assert len(results) == 1
+ assert results[0] == {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "foo", "namespace": "foobar"},
+ }
+
+
+def test_create_definitions_loads_list_kind():
+ params = {
+ "resource_definition": {
+ "kind": "PodList",
+ "apiVersion": "v1",
+ "items": [
+ {"kind": "Pod", "metadata": {"name": "foo"}},
+ {"kind": "Pod", "metadata": {"name": "bar"}},
+ ],
+ }
+ }
+ results = create_definitions(params)
+ assert len(results) == 2
+ assert results[0].name == "foo"
+ assert results[1].name == "bar"
+
+
+def test_merge_params_does_not_overwrite():
+ definition = {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "foo", "namespace": "bar"},
+ }
+ params = {
+ "kind": "Service",
+ "api_version": "v2",
+ "name": "baz",
+ "namespace": "gaz",
+ }
+ result = merge_params(definition, params)
+ assert result == definition
+
+
+def test_merge_params_adds_module_params():
+ params = {
+ "kind": "Pod",
+ "api_version": "v1",
+ "namespace": "bar",
+ "generate_name": "foo-",
+ }
+ result = merge_params({}, params)
+ assert result == {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"generateName": "foo-", "namespace": "bar"},
+ }
+
+
+def test_from_yaml_loads_string_docs():
+ definition = """
+kind: Pod
+apiVersion: v1
+metadata:
+ name: foo
+ namespace: bar
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: foo
+ namespace: bar
+"""
+ result = list(from_yaml(definition))
+ assert result[0]["kind"] == "Pod"
+ assert result[1]["kind"] == "ConfigMap"
+
+
+def test_from_yaml_loads_list():
+ definition = [
+ """
+ kind: Pod
+ apiVersion: v1
+ metadata:
+ name: foo
+ namespace: bar
+ """,
+ """
+ kind: ConfigMap
+ apiVersion: v1
+ metadata:
+ name: foo
+ namespace: bar
+ """,
+ {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "baz", "namespace": "bar"},
+ },
+ ]
+ result = list(from_yaml(definition))
+ assert len(result) == 3
+ assert result[0]["kind"] == "Pod"
+ assert result[1]["kind"] == "ConfigMap"
+ assert result[2]["metadata"]["name"] == "baz"
+
+
+def test_from_yaml_loads_dictionary():
+ definition = {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "foo", "namespace": "bar"},
+ }
+ result = list(from_yaml(definition))
+ assert result[0]["kind"] == "Pod"
+
+
+def test_from_file_loads_definitions():
+ current = Path(os.path.dirname(os.path.abspath(__file__)))
+ result = list(from_file(current / "fixtures/definitions.yml"))
+ assert result[0]["kind"] == "Namespace"
+ assert result[1]["kind"] == "Pod"
+
+
+def test_flatten_list_kind_flattens():
+ definition = {
+ "kind": "PodList",
+ "apiVersion": "v1",
+ "items": [
+ {"kind": "Pod", "metadata": {"name": "foo"}},
+ {"kind": "Pod", "metadata": {"name": "bar"}},
+ ],
+ }
+ result = flatten_list_kind(definition, {"namespace": "foobar"})
+ assert len(result) == 2
+
+ assert result[0]["kind"] == "Pod"
+ assert result[0]["apiVersion"] == "v1"
+ assert result[0]["metadata"]["name"] == "foo"
+ assert result[0]["metadata"]["namespace"] == "foobar"
+
+ assert result[1]["kind"] == "Pod"
+ assert result[1]["apiVersion"] == "v1"
+ assert result[1]["metadata"]["name"] == "bar"
+ assert result[1]["metadata"]["namespace"] == "foobar"
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_runner.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_runner.py
new file mode 100644
index 00000000..45c6f29a
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_runner.py
@@ -0,0 +1,135 @@
+import pytest
+from copy import deepcopy
+from unittest.mock import Mock
+
+from kubernetes.dynamic.resource import ResourceInstance
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import (
+ perform_action,
+)
+
+definition = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "foo",
+ "labels": {"environment": "production", "app": "nginx"},
+ "namespace": "foo",
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+modified_def = deepcopy(definition)
+modified_def["metadata"]["labels"]["environment"] = "testing"
+
+
+@pytest.mark.parametrize(
+ "action, params, existing, instance, expected",
+ [
+ (
+ "delete",
+ {"state": "absent"},
+ {},
+ {},
+ {"changed": False, "method": "delete", "result": {}},
+ ),
+ (
+ "delete",
+ {"state": "absent"},
+ definition,
+ {"kind": "Status"},
+ {"changed": True, "method": "delete", "result": {"kind": "Status"}},
+ ),
+ (
+ "apply",
+ {"apply": "yes"},
+ {},
+ definition,
+ {"changed": True, "method": "apply", "result": definition},
+ ),
+ (
+ "create",
+ {"state": "patched"},
+ {},
+ {},
+ {
+ "changed": False,
+ "result": {},
+ "warnings": [
+ "resource 'kind=Pod,name=foo' was not found but will not be created as 'state' parameter has been set to 'patched'"
+ ],
+ },
+ ),
+ (
+ "create",
+ {},
+ {},
+ definition,
+ {"changed": True, "method": "create", "result": definition},
+ ),
+ (
+ "replace",
+ {"force": "yes"},
+ definition,
+ definition,
+ {"changed": False, "method": "replace", "result": definition},
+ ),
+ (
+ "replace",
+ {"force": "yes"},
+ definition,
+ modified_def,
+ {"changed": True, "method": "replace", "result": modified_def},
+ ),
+ (
+ "update",
+ {},
+ definition,
+ definition,
+ {"changed": False, "method": "update", "result": definition},
+ ),
+ (
+ "update",
+ {},
+ definition,
+ modified_def,
+ {"changed": True, "method": "update", "result": modified_def},
+ ),
+ (
+ "create",
+ {"label_selectors": ["app=foo"]},
+ {},
+ definition,
+ {
+ "changed": False,
+ "msg": "resource 'kind=Pod,name=foo,namespace=foo' filtered by label_selectors.",
+ },
+ ),
+ (
+ "create",
+ {"label_selectors": ["app=nginx"]},
+ {},
+ definition,
+ {"changed": True, "method": "create", "result": definition},
+ ),
+ ],
+)
+def test_perform_action(action, params, existing, instance, expected):
+ svc = Mock()
+ svc.find_resource.return_value = Mock(
+ kind=definition["kind"], group_version=definition["apiVersion"]
+ )
+ svc.retrieve.return_value = ResourceInstance(None, existing) if existing else None
+ spec = {action + ".return_value": instance}
+ svc.configure_mock(**spec)
+
+ result = perform_action(svc, definition, params)
+ assert expected.items() <= result.items()
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_selector.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_selector.py
new file mode 100644
index 00000000..466ac096
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_selector.py
@@ -0,0 +1,204 @@
+# Copyright [2021] [Red Hat, Inc.]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
+ LabelSelectorFilter,
+ Selector,
+)
+
+prod_definition = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "test",
+ "labels": {"environment": "production", "app": "nginx"},
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+no_label_definition = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {"name": "test", "labels": {}},
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+test_definition = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {"name": "test", "labels": {"environment": "test", "app": "nginx"}},
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.15.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+
+def test_selector_parser():
+ f_selector = "environment==true"
+ sel = Selector(f_selector)
+ assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
+ f_selector = "environment=true"
+ sel = Selector(f_selector)
+ assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
+ f_selector = " environment == true "
+ sel = Selector(f_selector)
+ assert sel._operator == "in" and sel._data == ["true"] and sel._key == "environment"
+ f_selector = "environment!=false"
+ sel = Selector(f_selector)
+ assert (
+ sel._operator == "notin"
+ and sel._data == ["false"]
+ and sel._key == "environment"
+ )
+ f_selector = "environment notin (true, false)"
+ sel = Selector(f_selector)
+ assert (
+ sel._operator == "notin"
+ and "true" in sel._data
+ and "false" in sel._data
+ and sel._key == "environment"
+ )
+ f_selector = "environment in (true, false)"
+ sel = Selector(f_selector)
+ assert (
+ sel._operator == "in"
+ and "true" in sel._data
+ and "false" in sel._data
+ and sel._key == "environment"
+ )
+ f_selector = "environmentin(true, false)"
+ sel = Selector(f_selector)
+ assert not sel._operator and not sel._data and sel._key == f_selector
+ f_selector = "environment notin (true, false"
+ sel = Selector(f_selector)
+ assert not sel._operator and not sel._data and sel._key == f_selector
+ f_selector = "!environment"
+ sel = Selector(f_selector)
+ assert sel._operator == "!" and not sel._data and sel._key == "environment"
+ f_selector = "! environment "
+ sel = Selector(f_selector)
+ assert sel._operator == "!" and not sel._data and sel._key == "environment"
+
+
+def test_label_selector_without_operator():
+ label_selector = ["environment", "app"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+
+
+def test_label_selector_equal_operator():
+ label_selector = ["environment==test"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment=production"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment=production", "app==mongodb"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment=production", "app==nginx"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment", "app==nginx"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+
+
+def test_label_selector_notequal_operator():
+ label_selector = ["environment!=test"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment!=production"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment=production", "app!=mongodb"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment=production", "app!=nginx"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment", "app!=nginx"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+
+
+def test_label_selector_conflicting_definition():
+ label_selector = ["environment==test", "environment!=test"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment==test", "environment==production"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+
+
+def test_set_based_requirement():
+ label_selector = ["environment in (production)"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment in (production, test)"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment notin (production)"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment notin (production, test)"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["environment"]
+ assert LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(test_definition)
+ label_selector = ["!environment"]
+ assert not LabelSelectorFilter(label_selector).isMatching(prod_definition)
+ assert LabelSelectorFilter(label_selector).isMatching(no_label_definition)
+ assert not LabelSelectorFilter(label_selector).isMatching(test_definition)
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_service.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_service.py
new file mode 100644
index 00000000..a1822de6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_service.py
@@ -0,0 +1,292 @@
+from unittest.mock import Mock
+
+import pytest
+from kubernetes.dynamic.resource import ResourceInstance, Resource
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
+ K8sService,
+ diff_objects,
+)
+
+from kubernetes.dynamic.exceptions import NotFoundError
+
+pod_definition = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "foo",
+ "labels": {"environment": "production", "app": "nginx"},
+ "namespace": "foo",
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+pod_definition_updated = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "foo",
+ "labels": {"environment": "testing", "app": "nginx"},
+ "namespace": "bar",
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:1.14.2",
+ "command": ["/bin/sh", "-c", "sleep 10"],
+ }
+ ]
+ },
+}
+
+
+@pytest.fixture(scope="module")
+def mock_pod_resource_instance():
+ return ResourceInstance(None, pod_definition)
+
+
+@pytest.fixture(scope="module")
+def mock_pod_updated_resource_instance():
+ return ResourceInstance(None, pod_definition_updated)
+
+
+def test_diff_objects_no_diff():
+ match, diff = diff_objects(pod_definition, pod_definition)
+
+ assert match is True
+ assert diff == {}
+
+
+def test_diff_objects_meta_diff():
+ match, diff = diff_objects(pod_definition, pod_definition_updated)
+
+ assert match is False
+ assert diff["before"] == {
+ "metadata": {"labels": {"environment": "production"}, "namespace": "foo"}
+ }
+ assert diff["after"] == {
+ "metadata": {"labels": {"environment": "testing"}, "namespace": "bar"}
+ }
+
+
+def test_diff_objects_spec_diff():
+ pod_definition_updated = {
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "name": "foo",
+ "labels": {"environment": "production", "app": "nginx"},
+ "namespace": "foo",
+ },
+ "spec": {
+ "containers": [
+ {
+ "name": "busybox",
+ "image": "busybox",
+ "command": ["/bin/sh", "-c", "sleep 3600"],
+ }
+ ]
+ },
+ }
+ match, diff = diff_objects(pod_definition, pod_definition_updated)
+
+ assert match is False
+ assert diff["before"]["spec"] == pod_definition["spec"]
+ assert diff["after"]["spec"] == pod_definition_updated["spec"]
+
+
+def test_find_resource():
+ mock_pod_resource = Resource(
+ api_version="v1", kind="Pod", namespaced=False, preferred=True, prefix="api"
+ )
+ spec = {"resource.return_value": mock_pod_resource}
+ client = Mock(**spec)
+ svc = K8sService(client, Mock())
+ resource = svc.find_resource("Pod", "v1")
+
+ assert isinstance(resource, Resource)
+ assert resource.to_dict().items() <= mock_pod_resource.to_dict().items()
+
+
+def test_service_delete_existing_resource(mock_pod_resource_instance):
+ spec = {"delete.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock(
+ params={"delete_options": {"gracePeriodSeconds": 2}}, check_mode=False
+ )
+ resource = Mock()
+ svc = K8sService(client, module)
+ result = svc.delete(resource, pod_definition, mock_pod_resource_instance)
+
+ assert isinstance(result, dict)
+ assert result == mock_pod_resource_instance.to_dict()
+ client.delete.assert_called_with(
+ resource,
+ name=pod_definition["metadata"]["name"],
+ namespace=pod_definition["metadata"]["namespace"],
+ body={"apiVersion": "v1", "kind": "DeleteOptions", "gracePeriodSeconds": 2},
+ )
+
+
+def test_service_delete_no_existing_resource():
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ client = Mock()
+ client.delete.return_value = mock_pod_resource_instance
+ svc = K8sService(client, module)
+ result = svc.delete(Mock(), pod_definition)
+
+ assert result == {}
+ client.delete.assert_not_called()
+
+
+def test_service_delete_existing_resource_check_mode(mock_pod_resource_instance):
+ module = Mock(params={}, check_mode=True)
+ client = Mock(dry_run=False)
+ client.delete.return_value = mock_pod_resource_instance
+ svc = K8sService(client, module)
+ result = svc.delete(Mock(), pod_definition, mock_pod_resource_instance)
+
+ assert result == {}
+ client.delete.assert_not_called()
+
+
+def test_service_create_resource(mock_pod_resource_instance):
+ spec = {"create.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ result = svc.create(Mock(), pod_definition)
+
+ assert result == mock_pod_resource_instance.to_dict()
+
+
+def test_service_create_resource_check_mode():
+ client = Mock(dry_run=False)
+ client.create.return_value = mock_pod_resource_instance
+ module = Mock(params={}, check_mode=True)
+ svc = K8sService(client, module)
+ result = svc.create(Mock(), pod_definition)
+
+ assert result == pod_definition
+ client.create.assert_not_called()
+
+
+def test_service_retrieve_existing_resource(mock_pod_resource_instance):
+ spec = {"get.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ svc = K8sService(client, module)
+ results = svc.retrieve(Mock(), pod_definition)
+
+ assert isinstance(results, ResourceInstance)
+ assert results.to_dict() == pod_definition
+
+
+def test_service_retrieve_no_existing_resource():
+ spec = {"get.side_effect": [NotFoundError(Mock())]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ svc = K8sService(client, module)
+ results = svc.retrieve(Mock(), pod_definition)
+
+ assert results is None
+
+
+def test_create_project_request():
+ project_definition = {
+ "apiVersion": "v1",
+ "kind": "ProjectRequest",
+ "metadata": {"name": "test"},
+ }
+ spec = {"create.side_effect": [ResourceInstance(None, project_definition)]}
+ client = Mock(**spec)
+ module = Mock()
+ module.check_mode = False
+ module.params = {"state": "present"}
+ svc = K8sService(client, module)
+ results = svc.create_project_request(project_definition)
+
+ assert isinstance(results, dict)
+ assert results["changed"] is True
+ assert results["result"] == project_definition
+
+
+def test_service_apply_existing_resource(mock_pod_resource_instance):
+ spec = {"apply.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {"apply": True}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ result = svc.apply(Mock(), pod_definition_updated, mock_pod_resource_instance)
+
+ assert result == mock_pod_resource_instance.to_dict()
+
+
+def test_service_replace_existing_resource(mock_pod_resource_instance):
+ spec = {"replace.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance)
+
+ assert result == mock_pod_resource_instance.to_dict()
+
+
+def test_service_update_existing_resource(mock_pod_resource_instance):
+ spec = {"replace.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance)
+
+ assert result == mock_pod_resource_instance.to_dict()
+
+
+def test_service_find(mock_pod_resource_instance):
+ spec = {"get.side_effect": [mock_pod_resource_instance]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ results = svc.find("Pod", "v1", name="foo", namespace="foo")
+
+ assert isinstance(results, dict)
+ assert results["api_found"] is True
+ assert results["resources"] is not []
+ assert len(results["resources"]) == 1
+ assert results["resources"][0] == pod_definition
+
+
+def test_service_find_error():
+ spec = {"get.side_effect": [NotFoundError(Mock())]}
+ client = Mock(**spec)
+ module = Mock()
+ module.params = {}
+ module.check_mode = False
+ svc = K8sService(client, module)
+ results = svc.find("Pod", "v1", name="foo", namespace="foo")
+
+ assert isinstance(results, dict)
+ assert results["api_found"] is True
+ assert results["resources"] == []
diff --git a/ansible_collections/kubernetes/core/tests/unit/module_utils/test_waiter.py b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_waiter.py
new file mode 100644
index 00000000..b5ce10a5
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/module_utils/test_waiter.py
@@ -0,0 +1,122 @@
+import os
+import time
+from pathlib import Path
+from unittest.mock import Mock
+
+import pytest
+import yaml
+from kubernetes.dynamic.resource import ResourceInstance
+from kubernetes.dynamic.exceptions import NotFoundError
+
+from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
+ clock,
+ custom_condition,
+ deployment_ready,
+ DummyWaiter,
+ exists,
+ get_waiter,
+ pod_ready,
+ resource_absent,
+ Waiter,
+)
+
+
+def resources(filepath):
+ current = Path(os.path.dirname(os.path.abspath(__file__)))
+ with open(current / filepath) as fp:
+ return [ResourceInstance(None, d) for d in yaml.safe_load_all(fp)]
+
+
+RESOURCES = resources("fixtures/definitions.yml")
+PODS = resources("fixtures/pods.yml")
+DEPLOYMENTS = resources("fixtures/deployments.yml")
+
+
+def test_clock_times_out():
+ start = time.monotonic()
+ for x in clock(5, 1):
+ pass
+ elapsed = int(time.monotonic() - start)
+ assert x == 5
+ assert 5 <= elapsed <= 6
+
+
+@pytest.mark.parametrize(
+ "resource,expected",
+ zip(RESOURCES + [None, {}], [True, True, True, False, False, False]),
+)
+def test_exists_and_absent_checks_for_existence(resource, expected):
+ assert exists(resource) is expected
+ assert resource_absent(resource) is not expected
+
+
+@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, True, True]))
+def test_pod_ready_checks_readiness(pod, expected):
+ assert pod_ready(pod) is expected
+
+
+@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, False, False]))
+def test_custom_condition_checks_readiness(pod, expected):
+ condition = {"type": "www.example.com/gate", "status": "True"}
+ assert custom_condition(condition, pod) is expected
+
+
+@pytest.mark.parametrize("deployment,expected", zip(DEPLOYMENTS, [True, False]))
+def test_deployment_ready_checks_readiness(deployment, expected):
+ assert deployment_ready(deployment) is expected
+
+
+def test_dummywaiter_returns_resource_immediately():
+ resource = {
+ "kind": "Pod",
+ "apiVersion": "v1",
+ "metadata": {"name": "foopod", "namespace": "foobar"},
+ }
+ result, instance, elapsed = DummyWaiter().wait(resource, 10, 100)
+ assert result is True
+ assert instance == resource
+ assert elapsed == 0
+
+
+def test_waiter_waits_for_missing_resource():
+ spec = {"get.side_effect": NotFoundError(Mock())}
+ client = Mock(**spec)
+ resource = Mock()
+ result, instance, elapsed = Waiter(client, resource, exists).wait(
+ timeout=3,
+ sleep=1,
+ name=RESOURCES[0]["metadata"].get("name"),
+ namespace=RESOURCES[0]["metadata"].get("namespace"),
+ )
+ assert result is False
+ assert instance == {}
+ assert abs(elapsed - 3) <= 1
+
+
+@pytest.mark.parametrize("resource,expected", zip(RESOURCES, [True, True, True, False]))
+def test_waiter_waits_for_resource_to_exist(resource, expected):
+ result = resource.to_dict()
+ spec = {"get.side_effect": [NotFoundError(Mock()), resource, resource, resource]}
+ client = Mock(**spec)
+ success, instance, elapsed = Waiter(client, Mock(), exists).wait(
+ timeout=3,
+ sleep=1,
+ name=result["metadata"].get("name"),
+ namespace=result["metadata"].get("namespace"),
+ )
+ assert success is expected
+ assert instance == result
+ assert abs(elapsed - 2) <= 1
+
+
+def test_get_waiter_returns_correct_waiter():
+ assert get_waiter(Mock(), PODS[0]).predicate == pod_ready
+ waiter = get_waiter(Mock(), PODS[0], check_mode=True)
+ assert isinstance(waiter, DummyWaiter)
+ assert get_waiter(Mock(), PODS[0], state="absent").predicate == resource_absent
+ assert (
+ get_waiter(
+ Mock(), PODS[0], condition={"type": "Ready", "status": "True"}
+ ).predicate.func
+ == custom_condition
+ )
diff --git a/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template.py b/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template.py
new file mode 100644
index 00000000..c4657af6
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2020, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import argparse
+
+from ansible_collections.kubernetes.core.plugins.modules.helm_template import template
+
+
+def test_template_with_release_values_and_values_files():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+ parser.add_argument("-f", action="append")
+
+ rv = {"v1": {"enabled": True}}
+ vf = ["values1.yml", "values2.yml"]
+ mytemplate = template(
+ cmd=helm_cmd, chart_ref=my_chart_ref, release_values=rv, values_files=vf
+ )
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+
+ # helm_template writes release_values to temporary file with changing name
+ # these tests should insure
+ # - correct order values_files
+ # - unknown being included as last
+ assert args.f[0] == "values1.yml"
+ assert args.f[1] == "values2.yml"
+ assert len(args.f) == 3
+
+
+def test_template_with_one_show_only_template():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+ parser.add_argument("-f", action="append")
+ parser.add_argument("-s", action="append")
+
+ rv = {"revision": "1-13-0", "revisionTags": ["canary"]}
+ so_string = "templates/revision-tags.yaml"
+ so = [so_string]
+ mytemplate = template(
+ cmd=helm_cmd, chart_ref=my_chart_ref, show_only=so, release_values=rv
+ )
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+ print(mytemplate)
+ print(args)
+
+ assert len(args.f) == 1
+ assert len(args.s) == 1
+ assert args.s[0] == so_string
+
+
+def test_template_with_two_show_only_templates():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+ parser.add_argument("-f", action="append")
+ parser.add_argument("-s", action="append")
+
+ rv = {"revision": "1-13-0", "revisionTags": ["canary"]}
+ so_string_1 = "templates/revision-tags.yaml"
+ so_string_2 = "templates/some-dummy-template.yaml"
+ so = [so_string_1, so_string_2]
+ mytemplate = template(
+ cmd=helm_cmd, chart_ref=my_chart_ref, show_only=so, release_values=rv
+ )
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+
+ assert len(args.f) == 1
+ assert len(args.s) == 2
+ assert args.s[0] == so_string_1
+ assert args.s[1] == so_string_2
+
+
+def test_template_with_release_namespace():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+ parser.add_argument("-n", action="append")
+
+ ns = "istio-ingress-canary"
+ mytemplate = template(cmd=helm_cmd, chart_ref=my_chart_ref, release_namespace=ns)
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+
+ assert len(args.n) == 1
+ assert args.n[0] == ns
+
+
+def test_template_with_name():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ release_name = "mytestrelease"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+
+ mytemplate = template(
+ cmd=helm_cmd, chart_ref=my_chart_ref, release_name=release_name
+ )
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+
+ assert args.NAME == release_name
+
+
+def test_template_with_disablehook():
+ my_chart_ref = "testref"
+ helm_cmd = "helm"
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("cmd")
+ parser.add_argument("template")
+ # to "simulate" helm template options, include two optional parameters NAME and CHART.
+ # if parsed string contains only one parameter, the value will be passed
+ # to CHART and NAME will be set to default value "release-name" as in helm template
+ parser.add_argument("NAME", nargs="?", default="release-name")
+ parser.add_argument("CHART", nargs="+")
+ parser.add_argument("--no-hooks", dest="no_hooks", action="store_true")
+ parser.set_defaults(no_hooks=False)
+
+ mytemplate = template(cmd=helm_cmd, chart_ref=my_chart_ref, disable_hook=True)
+
+ args, unknown = parser.parse_known_args(mytemplate.split())
+
+ assert args.no_hooks is True
diff --git a/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template_module.py b/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template_module.py
new file mode 100644
index 00000000..9fd98e4c
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/modules/test_helm_template_module.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import unittest
+
+from unittest.mock import patch
+
+from ansible.module_utils import basic
+from ansible_collections.kubernetes.core.plugins.modules import helm_template
+from ansible_collections.kubernetes.core.tests.unit.utils.ansible_module_mock import (
+ AnsibleFailJson,
+ AnsibleExitJson,
+ exit_json,
+ fail_json,
+ get_bin_path,
+ set_module_args,
+)
+
+
+class TestDependencyUpdateWithoutChartRepoUrlOption(unittest.TestCase):
+ def setUp(self):
+ self.mock_module_helper = patch.multiple(
+ basic.AnsibleModule,
+ exit_json=exit_json,
+ fail_json=fail_json,
+ get_bin_path=get_bin_path,
+ )
+ self.mock_module_helper.start()
+
+ # Stop the patch after test execution
+ # like tearDown but executed also when the setup failed
+ self.addCleanup(self.mock_module_helper.stop)
+
+ def test_module_fail_when_required_args_missing(self):
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ helm_template.main()
+
+ def test_dependency_update_option_not_defined(self):
+ set_module_args({"chart_ref": "/tmp/path"})
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm_template.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm template /tmp/path", environ_update={}
+ )
+ assert result.exception.args[0]["command"] == "/usr/bin/helm template /tmp/path"
+
+ def test_dependency_update_option_false(self):
+ set_module_args(
+ {
+ "chart_ref": "test",
+ "chart_repo_url": "https://charts.com/test",
+ "dependency_update": False,
+ }
+ )
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm_template.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm template test --repo=https://charts.com/test",
+ environ_update={},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm template test --repo=https://charts.com/test"
+ )
+
+ def test_dependency_update_option_true(self):
+ set_module_args(
+ {"chart_ref": "https://charts/example.tgz", "dependency_update": True}
+ )
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm_template.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm template https://charts/example.tgz --dependency-update",
+ environ_update={},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm template https://charts/example.tgz --dependency-update"
+ )
diff --git a/ansible_collections/kubernetes/core/tests/unit/modules/test_module_helm.py b/ansible_collections/kubernetes/core/tests/unit/modules/test_module_helm.py
new file mode 100644
index 00000000..ca61cf3e
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/modules/test_module_helm.py
@@ -0,0 +1,497 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import unittest
+
+from unittest.mock import MagicMock, patch, call
+
+from ansible.module_utils import basic
+from ansible_collections.kubernetes.core.plugins.modules import helm
+from ansible_collections.kubernetes.core.tests.unit.utils.ansible_module_mock import (
+ AnsibleFailJson,
+ AnsibleExitJson,
+ exit_json,
+ fail_json,
+ get_bin_path,
+ set_module_args,
+)
+
+
+class TestDependencyUpdateWithoutChartRepoUrlOption(unittest.TestCase):
+ def setUp(self):
+ self.mock_module_helper = patch.multiple(
+ basic.AnsibleModule,
+ exit_json=exit_json,
+ fail_json=fail_json,
+ get_bin_path=get_bin_path,
+ )
+ self.mock_module_helper.start()
+
+ # Stop the patch after test execution
+ # like tearDown but executed also when the setup failed
+ self.addCleanup(self.mock_module_helper.stop)
+
+ self.chart_info_without_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ }
+
+ self.chart_info_with_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ "dependencies": [
+ {
+ "name": "test",
+ "version": "0.1.0",
+ "repository": "file://../test-chart",
+ }
+ ],
+ }
+
+ def test_module_fail_when_required_args_missing(self):
+ with self.assertRaises(AnsibleFailJson):
+ set_module_args({})
+ helm.main()
+
+ def test_dependency_update_option_not_defined(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "/tmp/path",
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ helm.run_dep_update = MagicMock()
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ helm.run_dep_update.assert_not_called()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm upgrade -i --reset-values test /tmp/path",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test /tmp/path"
+ )
+
+ def test_dependency_update_option_false(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "/tmp/path",
+ "dependency_update": False,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ helm.run_dep_update = MagicMock()
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ helm.run_dep_update.assert_not_called()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm upgrade -i --reset-values test /tmp/path",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test /tmp/path"
+ )
+
+ def test_dependency_update_option_true(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "/tmp/path",
+ "dependency_update": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_with_dep)
+
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = 0, "configuration updated", ""
+ with patch.object(basic.AnsibleModule, "warn") as mock_warn:
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_warn.assert_not_called()
+ mock_run_command.assert_has_calls(
+ [
+ call(
+ "/usr/bin/helm upgrade -i --reset-values test /tmp/path",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ ]
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test /tmp/path"
+ )
+
+ def test_dependency_update_option_true_without_dependencies_block(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "/tmp/path",
+ "dependency_update": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with patch.object(basic.AnsibleModule, "warn") as mock_warn:
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_warn.assert_called_once()
+ mock_run_command.assert_has_calls(
+ [
+ call(
+ "/usr/bin/helm upgrade -i --reset-values test /tmp/path",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ ]
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test /tmp/path"
+ )
+
+
+class TestDependencyUpdateWithChartRepoUrlOption(unittest.TestCase):
+ def setUp(self):
+ self.mock_module_helper = patch.multiple(
+ basic.AnsibleModule,
+ exit_json=exit_json,
+ fail_json=fail_json,
+ get_bin_path=get_bin_path,
+ )
+ self.mock_module_helper.start()
+
+ # Stop the patch after test execution
+ # like tearDown but executed also when the setup failed
+ self.addCleanup(self.mock_module_helper.stop)
+
+ self.chart_info_without_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ }
+
+ self.chart_info_with_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ "dependencies": [
+ {
+ "name": "test",
+ "version": "0.1.0",
+ "repository": "file://../test-chart",
+ }
+ ],
+ }
+
+ def test_dependency_update_option_not_defined(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "chart1",
+ "chart_repo_url": "http://repo.example/charts",
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1"
+ )
+
+ def test_dependency_update_option_False(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "chart1",
+ "chart_repo_url": "http://repo.example/charts",
+ "dependency_update": False,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1"
+ )
+
+ def test_dependency_update_option_True_and_replace_option_disabled(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "chart1",
+ "chart_repo_url": "http://repo.example/charts",
+ "dependency_update": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_with_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleFailJson) as result:
+ helm.main()
+ # mock_run_command.assert_called_once_with('/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1',
+ # environ_update={'HELM_NAMESPACE': 'test'})
+ assert result.exception.args[0]["msg"] == (
+ "'--dependency-update' hasn't been supported yet with 'helm upgrade'. "
+ "Please use 'helm install' instead by adding 'replace' option"
+ )
+ assert result.exception.args[0]["failed"]
+
+ def test_dependency_update_option_True_and_replace_option_enabled(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "chart1",
+ "chart_repo_url": "http://repo.example/charts",
+ "dependency_update": True,
+ "replace": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm --repo=http://repo.example/charts install --dependency-update --replace test chart1",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm --repo=http://repo.example/charts install --dependency-update --replace test chart1"
+ )
+
+
+class TestDependencyUpdateWithChartRefIsUrl(unittest.TestCase):
+ def setUp(self):
+ self.mock_module_helper = patch.multiple(
+ basic.AnsibleModule,
+ exit_json=exit_json,
+ fail_json=fail_json,
+ get_bin_path=get_bin_path,
+ )
+ self.mock_module_helper.start()
+
+ # Stop the patch after test execution
+ # like tearDown but executed also when the setup failed
+ self.addCleanup(self.mock_module_helper.stop)
+
+ self.chart_info_without_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ }
+
+ self.chart_info_with_dep = {
+ "apiVersion": "v2",
+ "appVersion": "default",
+ "description": "A chart used in molecule tests",
+ "name": "test-chart",
+ "type": "application",
+ "version": "0.1.0",
+ "dependencies": [
+ {
+ "name": "test",
+ "version": "0.1.0",
+ "repository": "file://../test-chart",
+ }
+ ],
+ }
+
+ def test_dependency_update_option_not_defined(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "http://repo.example/charts/application.tgz",
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm upgrade -i --reset-values test http://repo.example/charts/application.tgz",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test http://repo.example/charts/application.tgz"
+ )
+
+ def test_dependency_update_option_False(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "http://repo.example/charts/application.tgz",
+ "dependency_update": False,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm upgrade -i --reset-values test http://repo.example/charts/application.tgz",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm upgrade -i --reset-values test http://repo.example/charts/application.tgz"
+ )
+
+ def test_dependency_update_option_True_and_replace_option_disabled(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "http://repo.example/charts/application.tgz",
+ "dependency_update": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_with_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleFailJson) as result:
+ helm.main()
+ # mock_run_command.assert_called_once_with('/usr/bin/helm --repo=http://repo.example/charts upgrade -i --reset-values test chart1',
+ # environ_update={'HELM_NAMESPACE': 'test'})
+ assert result.exception.args[0]["msg"] == (
+ "'--dependency-update' hasn't been supported yet with 'helm upgrade'. "
+ "Please use 'helm install' instead by adding 'replace' option"
+ )
+ assert result.exception.args[0]["failed"]
+
+ def test_dependency_update_option_True_and_replace_option_enabled(self):
+ set_module_args(
+ {
+ "release_name": "test",
+ "release_namespace": "test",
+ "chart_ref": "http://repo.example/charts/application.tgz",
+ "dependency_update": True,
+ "replace": True,
+ }
+ )
+ helm.get_release_status = MagicMock(return_value=None)
+ helm.fetch_chart_info = MagicMock(return_value=self.chart_info_without_dep)
+ with patch.object(basic.AnsibleModule, "run_command") as mock_run_command:
+ mock_run_command.return_value = (
+ 0,
+ "configuration updated",
+ "",
+ ) # successful execution
+ with self.assertRaises(AnsibleExitJson) as result:
+ helm.main()
+ mock_run_command.assert_called_once_with(
+ "/usr/bin/helm install --dependency-update --replace test http://repo.example/charts/application.tgz",
+ environ_update={"HELM_NAMESPACE": "test"},
+ )
+ assert (
+ result.exception.args[0]["command"]
+ == "/usr/bin/helm install --dependency-update --replace test http://repo.example/charts/application.tgz"
+ )
diff --git a/ansible_collections/kubernetes/core/tests/unit/requirements.txt b/ansible_collections/kubernetes/core/tests/unit/requirements.txt
new file mode 100644
index 00000000..55c7255f
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/requirements.txt
@@ -0,0 +1,3 @@
+pytest
+PyYAML
+kubernetes
diff --git a/ansible_collections/kubernetes/core/tests/unit/utils/ansible_module_mock.py b/ansible_collections/kubernetes/core/tests/unit/utils/ansible_module_mock.py
new file mode 100644
index 00000000..4cb43751
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tests/unit/utils/ansible_module_mock.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# This module maock the AnsibleModule class for more information please visite
+# https://docs.ansible.com/ansible/latest/dev_guide/testing_units_modules.html#module-argument-processing
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import json
+
+from ansible.module_utils import basic
+from ansible.module_utils.common.text.converters import to_bytes
+
+
+def set_module_args(args):
+ """prepare arguments so that they will be picked up during module creation"""
+ args = json.dumps({"ANSIBLE_MODULE_ARGS": args})
+ basic._ANSIBLE_ARGS = to_bytes(args)
+
+
+class AnsibleExitJson(Exception):
+ """Exception class to be raised by module.exit_json and caught by the test case"""
+
+ pass
+
+
+class AnsibleFailJson(Exception):
+ """Exception class to be raised by module.fail_json and caught by the test case"""
+
+ pass
+
+
+def exit_json(*args, **kwargs):
+ """function to patch over exit_json; package return data into an exception"""
+ if "changed" not in kwargs:
+ kwargs["changed"] = False
+ raise AnsibleExitJson(kwargs)
+
+
+def fail_json(*args, **kwargs):
+ """function to patch over fail_json; package return data into an exception"""
+ kwargs["failed"] = True
+ raise AnsibleFailJson(kwargs)
+
+
+def get_bin_path(self, arg, required=False):
+ """Mock AnsibleModule.get_bin_path"""
+ if arg.endswith("helm"):
+ return "/usr/bin/helm"
+ else:
+ if required:
+ fail_json(msg="%r not found !" % arg)
+
+
+# def warn(self,msg):
+# return msg
diff --git a/ansible_collections/kubernetes/core/tox.ini b/ansible_collections/kubernetes/core/tox.ini
new file mode 100644
index 00000000..491046ec
--- /dev/null
+++ b/ansible_collections/kubernetes/core/tox.ini
@@ -0,0 +1,38 @@
+[tox]
+minversion = 1.4.2
+skipsdist = True
+
+[testenv:integration]
+install_command = pip install {opts} {packages}
+
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
+ https://github.com/ansible/ansible/archive/devel.tar.gz
+
+passenv =
+ HOME
+
+commands=
+ ansible-test integration -v --color --retry-on-error --diff --coverage --continue-on-error --python {posargs}
+
+[testenv:add_docs]
+deps = git+https://github.com/ansible-network/collection_prep
+commands = collection_prep_add_docs -p .
+
+[testenv:black]
+deps =
+ black >= 22.0, < 23.0
+
+commands =
+ black -v --check --diff {toxinidir}/plugins {toxinidir}/tests
+
+[testenv:linters]
+deps =
+ yamllint
+ flake8
+ {[testenv:black]deps}
+
+commands =
+ black -v --check --diff {toxinidir}/plugins {toxinidir}/tests
+ yamllint -s {toxinidir}
+ flake8 {toxinidir}