Repository: devopsspiral/KubeLibrary Branch: master Commit: 84532618cbf4 Files: 125 Total size: 593.6 KB Directory structure: gitextract_9_ebzae_/ ├── .circleci/ │ └── config.yml ├── .coveragerc ├── .flake8 ├── .github/ │ └── pull_request_template.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs/ │ └── index.html ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── src/ │ └── KubeLibrary/ │ ├── KubeLibrary.py │ ├── __init__.py │ ├── exceptions.py │ └── version.py ├── test/ │ ├── __init__.py │ ├── resources/ │ │ ├── cluster_role.json │ │ ├── cluster_role_bind.json │ │ ├── configmap.json │ │ ├── cronjob.json │ │ ├── cronjob_details.json │ │ ├── daemonset.json │ │ ├── daemonset_details.json │ │ ├── deployment.json │ │ ├── endpoints.json │ │ ├── hpa.json │ │ ├── hpa_details.json │ │ ├── ingress.json │ │ ├── ingress_details.json │ │ ├── jobs.json │ │ ├── k3d │ │ ├── multiple_context │ │ ├── namespaces.json │ │ ├── node_info.json │ │ ├── pod.json │ │ ├── pod_status.json │ │ ├── pods.json │ │ ├── pvc.json │ │ ├── replicaset.json │ │ ├── role.json │ │ ├── rolebinding.json │ │ ├── secrets.json │ │ ├── service.json │ │ ├── service_accounts.json │ │ ├── service_details.json │ │ └── sts.json │ └── test_KubeLibrary.py ├── test-objects-chart/ │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates/ │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── cluster_role.yaml │ │ ├── cluster_role_bind.yaml │ │ ├── cronjob.yaml │ │ ├── daemonset.yaml │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── job.yml │ │ ├── role.yaml │ │ ├── rolebinding.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests/ │ │ └── test-connection.yaml │ └── values.yaml └── testcases/ ├── Dockerfile ├── cluster_role/ │ ├── cluster_role.robot │ └── cluster_role_kw.robot ├── configmap/ │ ├── configmap.robot │ └── configmap_kw.robot ├── connect_GKE_clusters.robot ├── cronjob/ │ ├── cronjob.robot │ └── cronjob_kw.robot ├── custom_objects/ │ ├── ambassador_crds.robot │ └── custom_objects.robot ├── daemonset/ │ ├── daemonsets.robot │ └── daemonsets_kw.robot ├── deployment/ │ ├── deployment.robot │ └── deployment_kw.robot ├── dynamic_client/ │ ├── dynamic_client.robot │ ├── dynamic_client_kw.robot │ └── resources/ │ ├── pod.yaml │ ├── pod_generated_name.yaml │ ├── svc.yaml │ └── svc_lookup.yaml ├── exec/ │ ├── exec.robot │ └── exec_kw.robot ├── grafana/ │ ├── demo-UI-test.robot │ └── values.yaml ├── healthcheck/ │ ├── healthcheck.robot │ └── healthcheck_kw.robot ├── horizontalPodAutoscaler/ │ ├── hpa.robot │ └── hpa_kw.robot ├── ingress/ │ ├── ingress.robot │ └── ingress_kw.robot ├── job/ │ ├── job.robot │ └── job_kw.robot ├── namespace/ │ ├── namespace.robot │ └── namespace_kw.robot ├── pod/ │ ├── pod.robot │ └── pod_kw.robot ├── pvc/ │ ├── pvc.robot │ └── pvc_kw.robot ├── reload-config/ │ ├── reload-config.robot │ ├── reload-config_kw.robot │ └── sa.yaml ├── replicaset/ │ ├── replicaset.robot │ └── replicaset_kw.robot ├── requirements.txt ├── role/ │ ├── role.robot │ └── role_kw.robot ├── secrets/ │ ├── secret.robot │ └── secret_kw.robot ├── service/ │ ├── service.robot │ └── service_kw.robot ├── service_account/ │ ├── service_account.robot │ └── service_account_kw.robot ├── sts/ │ ├── sts.robot │ └── sts_kw.robot ├── system_smoke.robot ├── system_smoke_kw.robot └── test_version.robot ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2.1 orbs: python: circleci/python@0.3.2 k3d: devopsspiral/k3d@0.1.5 jobs: build-and-test: executor: name: python/default tag: "3.9" environment: PYTHONPATH=./src steps: - checkout - python/load-cache - python/install-deps: dependency-file: requirements-dev.txt - python/save-cache - python/test lint-and-coverage: executor: name: python/default tag: "3.9" environment: PYTHONPATH=./src steps: - checkout - python/install-deps: dependency-file: requirements-dev.txt - run: name: Linter command: | flake8 src/ flake8 test/ - run: name: Coverage command: | coverage run coverage report test-on-k8s: executor: name: python/default tag: "3.9" environment: PYTHONPATH=./src steps: - setup_remote_docker - checkout - run: name: Build Kubelibrary container image command: | docker build -t kubelibrary -f testcases/Dockerfile . - k3d/k3d-helpers - k3d/k3d-up: cluster-name: testk3d-2 k3s-version: latest k3s-bin-version: latest - k3d/k3d-run: step-name: Prerequisites for 2nd cluster command: | kubectl version kubectl create namespace test-ns-2 - k3d/k3d-up: cluster-name: testk3d-1 k3s-version: latest k3s-bin-version: latest - k3d/k3d-run: step-name: Prerequisites for 1st cluster command: | sleep 10 kubectl version helm repo add grafana https://grafana.github.io/helm-charts helm repo update helm install grafana grafana/grafana -f /repo/testcases/grafana/values.yaml export KLIB_POD_NAMESPACE=kubelib-tests kubectl create namespace $KLIB_POD_NAMESPACE kubectl label namespaces kubelib-tests test=test helm install kubelib-test /repo/test-objects-chart -n $KLIB_POD_NAMESPACE - k3d/k3d-run: step-name: Run Other examples command: | export KLIB_POD_NAMESPACE=kubelib-tests # Other tests docker run --rm \ --network container:k3d-${K3D_CLUSTER}-serverlb \ --volumes-from kubeconfig \ -e KUBECONFIG=$K3D_KUBECONFIG \ -e KLIB_POD_PATTERN='busybox.*' \ -e KLIB_POD_LABELS='job-name=busybox-job' \ -e KLIB_POD_NAMESPACE=$KLIB_POD_NAMESPACE \ kubelibrary -i other /testcases/ - k3d/k3d-run: step-name: Run Multi cluster examples command: | # Multi cluster tests kubectl create namespace test-ns-1 kubectl apply -f /repo/testcases/reload-config/sa.yaml MYSA_TOKEN_SECRET=mysa-token export K8S_TOKEN=$(kubectl get secret $MYSA_TOKEN_SECRET --template={{.data.token}} | base64 -d) kubectl get secret $MYSA_TOKEN_SECRET -o jsonpath="{.data.ca\.crt}" | base64 -d > ca.crt export K8S_CA_CRT=/.kube/ca.crt export KUBE_CONFIG1=/.kube/testk3d-1 export KUBE_CONFIG2=/.kube/testk3d-2 export CLUSTER1_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' k3d-testk3d-1-server-0) export CLUSTER2_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' k3d-testk3d-2-server-0) export K8S_API_URL="https://$CLUSTER1_IP:6443" docker cp kubeconfig:$KUBE_CONFIG1 ~/kubeconfig-testk3d-1.yaml docker cp kubeconfig:$KUBE_CONFIG2 ~/kubeconfig-testk3d-2.yaml sed -i "s#server: https://.*#server: https://$CLUSTER1_IP:6443#g" ~/kubeconfig-testk3d-1.yaml sed -i "s#server: https://.*#server: https://$CLUSTER2_IP:6443#g" ~/kubeconfig-testk3d-2.yaml docker cp ~/kubeconfig-testk3d-1.yaml kubeconfig:$KUBE_CONFIG1 docker cp ~/kubeconfig-testk3d-2.yaml kubeconfig:$KUBE_CONFIG2 docker cp ca.crt kubeconfig:$K8S_CA_CRT docker create --rm -it \ --network k3d-testk3d-1 \ --volumes-from kubeconfig \ -e KUBE_CONFIG1=$KUBE_CONFIG1 \ -e KUBE_CONFIG2=$KUBE_CONFIG2 \ -e K8S_API_URL=$K8S_API_URL \ -e K8S_TOKEN=$K8S_TOKEN \ -e K8S_CA_CRT=$K8S_CA_CRT \ --name kubelibrary kubelibrary -i reload-config /testcases/ docker network connect k3d-testk3d-2 kubelibrary docker start -a kubelibrary - k3d/k3d-run: step-name: Run Smoke examples command: | K8S_VERSION=$(echo ${K3D_CLUSTER_VERSION:1} | cut -d "-" -f1) docker run --rm \ --network container:k3d-${K3D_CLUSTER}-serverlb \ --volumes-from kubeconfig \ -e KUBECONFIG=$K3D_KUBECONFIG \ -e KUBELET_VERSION=$K8S_VERSION \ kubelibrary -i smoke /testcases/ - k3d/k3d-run: step-name: Run Grafana examples command: | # Grafana tests K8S_VERSION=$(echo ${K3D_CLUSTER_VERSION:1} | cut -d "-" -f1) docker run --rm \ --network container:k3d-${K3D_CLUSTER}-serverlb \ --volumes-from kubeconfig \ -e KUBECONFIG=$K3D_KUBECONFIG \ -e KLIB_POD_PATTERN='grafana.*' \ -e KLIB_POD_ANNOTATIONS='{"kubelibrary":"testing"}' \ -e KLIB_POD_LABELS='{"app.kubernetes.io/name":"grafana"}' \ -e KLIB_POD_NAMESPACE=default \ -e KLIB_RESOURCE_LIMITS_MEMORY=128Mi \ -e KLIB_RESOURCE_REQUESTS_CPU=250m \ -e KLIB_RESOURCE_LIMITS_CPU=500m \ -e KLIB_RESOURCE_REQUESTS_MEMORY=64Mi \ -e KUBELET_VERSION=$K8S_VERSION \ kubelibrary -i grafana /testcases/ publish-to-pypi: executor: name: python/default tag: "3.9" environment: PYTHONPATH=./src steps: - checkout - run: name: Verify setup.py version matches tag command: | SEMVER="${CIRCLE_TAG:1}" grep "## \[$SEMVER\]" CHANGELOG.md - run: name: Publish on Pypi command: | pip install twine python3 setup.py sdist bdist_wheel python3 -m twine upload dist/* workflows: main: jobs: - build-and-test: filters: tags: only: /.*/ - lint-and-coverage: filters: tags: only: /.*/ - test-on-k8s: filters: tags: only: /.*/ - publish-to-pypi: requires: - build-and-test - lint-and-coverage - test-on-k8s filters: branches: ignore: /.*/ tags: only: /^v.*/ ================================================ FILE: .coveragerc ================================================ [run] command_line = -m unittest discover source = src/ [report] fail_under = 84 ================================================ FILE: .flake8 ================================================ [flake8] max-line-length = 160 ================================================ FILE: .github/pull_request_template.md ================================================ \ \ Fixes #\ Before merge following needs to be applied: - [ ] At least one example testcase added in testcases/ - [ ] Library Documentation regenerated according to [Generate docs](https://github.com/devopsspiral/KubeLibrary#generate-docs) - [ ] PR entry added in CHANGELOG.md in **In progress** section - [ ] All new testcases tagged as **prerelease** along other tags to exclude it from execution until released on PyPI - [ ] Coverage threshold increased in [.coveragerc](https://github.com/devopsspiral/KubeLibrary/blob/master/.coveragerc) if new coverage is higher than actual, see the lint-and-coverage step in CI ``` fail_under = 86 ``` ================================================ FILE: .gitignore ================================================ .venv .idea .vscode .coverage *.pyc *.pyo src/robotframework_kubelibrary.egg-info/ dist/ build/ log.html output.xml report.html ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## In progress ## [0.8.10] - 2025-12-16 ### Added - Add a keyword to get the Kubernetes Cluster Version ## [0.8.9] - 2025-07-17 ### Fixed - Fixed proxy settings which are now handled by kubernetes python client ## [0.8.8] - 2024-10-07 ### Added - update create keyword to return created object [#143](https://github.com/devopsspiral/KubeLibrary/pull/143) by [@aisonaku] ## [0.8.7] - 2023-11-19 ### Added - update read_namespaced_pod_log keyword with new optional parameter 'since_seconds' [#136](https://github.com/devopsspiral/KubeLibrary/pull/136) by [@LissaGreense] - google-auth>=2.5.0 [#138](https://github.com/devopsspiral/KubeLibrary/pull/138) by [@angegar] ## [0.8.6] - 2023-05-27 ### Added - add new keyword to list all namespaced custom objects [#133] (https://github.com/devopsspiral/KubeLibrary/pull/133) by [@kutayy] ## [0.8.5] - 2023-04-22 ### Fixed - Fixed no_proxy setting ## [0.8.4] - 2023-04-10 ### Fixed - Fixed proxy setting - Deprecating batch/v1beta1, discovery.k8s.io/v1beta1 ## [0.8.3] - 2022-12-19 revert - Add proxy configuration fetched from `HTTP_PROXY` or `http_proxy` environment variable by [@LissaGreense] ## [0.8.0] - 2022-10-27 ### Added - Add function list_namespaced_stateful_set_by_pattern [#114](https://github.com/devopsspiral/KubeLibrary/pull/113) by [@siaomingjeng](https://github.com/siaomingjeng) - Add function list_namespaced_persistent_volume_claim_by_pattern [#112](https://github.com/devopsspiral/KubeLibrary/pull/112) by [@siaomingjeng](https://github.com/siaomingjeng) ### Changed - batchv1_beta1 deprecated - ci uses latest default k8s and latest k3s - octopus test helm chart removed as not working ## [0.7.0] - 2022-04-01 ### Added - Added keyword for handling kubectl exec [#102](https://github.com/devopsspiral/KubeLibrary/pull/101) by [@MarcinMaciaszek](https://github.com/MarcinMaciaszek) ### Changed - networkingv1api used instead of extensionsv1beta1 ## [0.6.2] - 2022-02-25 ### Fixed - Fix the kubernetes lib version (21.7.0) to still support extension/v1beta1 (ingress) ## [0.6.1] - 2022-01-27 ### Changed - Refactored setup.py & requirements, moved library scope to GLOBAL, sperated exceptions [#101](https://github.com/devopsspiral/KubeLibrary/pull/101) by [@MarcinMaciaszek](https://github.com/MarcinMaciaszek) ### Fixed - Generate keyword documentation without a kubernetes cluster [#103](https://github.com/devopsspiral/KubeLibrary/pull/103) by [bli74](https://github.com/bli74) ## [0.6.0] - 2021-11-30 ### Changed - Helpers and keywords unification [#75](https://github.com/devopsspiral/KubeLibrary/pull/75) by [@m-wcislo](https://github.com/m-wcislo) ## [0.5.0] - 2021-10-03 ### Added - Dynamic client support and some utilities [#93](https://github.com/devopsspiral/KubeLibrary/pull/93) by [@mertkayhan](https://github.com/mertkayhan) - Keyword for getting Horizontal Pod Autoscalers [#80](https://github.com/devopsspiral/KubeLibrary/pull/80 )by [@Nilsty](https://github.com/Nilsty) - Keyword for list cluster role and cluster role binding [#58](https://github.com/devopsspiral/KubeLibrary/pull/58) by [@satish-nubolab](https://github.com/satish-nubolab) - Keyword for getiing role and rolebinding [#56](https://github.com/devopsspiral/KubeLibrary/pull/56) by [@satish-nubolab](https://github.com/satish-nubolab) - Bearer token authentication [#39](https://github.com/devopsspiral/KubeLibrary/pull/39) by [@m-wcislo](https://github.com/m-wcislo) - Keyoword for create and delete a cronjob [#71](https://github.com/devopsspiral/KubeLibrary/pull/71) by [@satish-nubolab](https://github.com/satish-nubolab) - Keywords for get replicaset in a namespace [#82](https://github.com/devopsspiral/KubeLibrary/pull/92) by [@hello2ray](https://github.com/hello2ray) ## [0.4.0] - 2021-03-12 ### Added - Kubeconfig context support [#36](https://github.com/devopsspiral/KubeLibrary/pull/36) by [@m-wcislo](https://github.com/m-wcislo) - Keyword for getting secrets [#31](https://github.com/devopsspiral/KubeLibrary/pull/31 )by [@Nilsty](https://github.com/Nilsty) - Keyword for cluster healthcheck [#40](https://github.com/devopsspiral/KubeLibrary/pull/40) by [@satish-nubolab](https://github.com/satish-nubolab) - Extend cluster healthcheck [#47](https://github.com/devopsspiral/KubeLibrary/pull/47) by [@mika-b](https://github.com/mika-b) - Keyword for list ingress [#38](https://github.com/devopsspiral/KubeLibrary/pull/38) by [@satish-nubolab](https://github.com/satish-nubolab) - Keyword for list cronjob [#48](https://github.com/devopsspiral/KubeLibrary/pull/48) by [@satish-nubolab](https://github.com/satish-nubolab) - Keyword for list daemonset [#50](https://github.com/devopsspiral/KubeLibrary/pull/50) by [@satish-nubolab](https://github.com/satish-nubolab) - Keyword for CustomObjectsApi [#54](https://github.com/devopsspiral/KubeLibrary/pull/54) by [@mika-b](https://github.com/mika-b) - Example tests for Ambassador CRDs [#63](https://github.com/devopsspiral/KubeLibrary/pull/63) by [@Nilsty](https://github.com/Nilsty) ### Fixed - Fix for cert validation disabling not being possible for all api clients [#61](https://github.com/devopsspiral/KubeLibrary/pull/61) by [@m-wcislo](https://github.com/m-wcislo) ### Fixed - cert_validation=False was not affecting all used APIs ## [0.3.0] - 2021-02-01 ### Added - CI implementation [#14](https://github.com/devopsspiral/KubeLibrary/pull/14) by [@m-wcislo](https://github.com/m-wcislo) - keywords to list deployments [#13](https://github.com/devopsspiral/KubeLibrary/pull/13) by [@Nilsty](https://github.com/Nilsty) - keywords for get/create/delete service accounts [#28](https://github.com/devopsspiral/KubeLibrary/pull/28) by [@kutayy](https://github.com/kutayy) ## [0.2.0] - 2020-09-03 ### Added - Update docs with latest libdoc version [#11](https://github.com/devopsspiral/KubeLibrary/pull/11) by [@Nilsty](https://github.com/Nilsty) - Example test case to connect to a GKE cluster [#10](https://github.com/devopsspiral/KubeLibrary/pull/10) by [@Nilsty](https://github.com/Nilsty) - Adding label selectors [#9](https://github.com/devopsspiral/KubeLibrary/pull/9) by [@Nilsty](https://github.com/Nilsty) - Adding keyword to Reload the configuration of the KubeLibrary [#8](https://github.com/devopsspiral/KubeLibrary/pull/8) by [@Nilsty](https://github.com/Nilsty) - Adding keyword and tests for "Get Pod Logs" [#7](https://github.com/devopsspiral/KubeLibrary/pull/7) by [@Nilsty](https://github.com/Nilsty) - Adding keyword to read jobs [#6](https://github.com/devopsspiral/KubeLibrary/pull/6) by [@Nilsty](https://github.com/Nilsty) ## [0.1.4] - 2020-07-28 ### Added - pod generic testcases added, should be extended in future - add kw for getting configmaps, update docs [#5](https://github.com/devopsspiral/KubeLibrary/pull/5) by [@Nilsty](https://github.com/Nilsty) ### Changed - reorganized library functions to getters, filters and asserts - python unit tests ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing We welcome contributions of all types including: * Proposing new features * Reporting a bug * Fixing a bug * Enhancing documentation * Materials describing the usage of KubeLibrary that we could link from this repository ## Reporting issues We use [GitHub issues](https://github.com/devopsspiral/KubeLibrary/issues) for reporting bugs, feature proposals and discussions. When reporting a bug, please include following information: * Summary of the problem and context * Steps to reproduce. We are mostly using k3s/k3d and kind as a test clusters, if you could reproduce your problem there that would be easier for others to follow. Attach logs, files and anything that was used and might be helpful in investigation. * What you expected * What actually happened * Comments, including your understanding of a problem, possible fix, etc. ## Pull Request checklist * Create an issue first if you expect the topic needs some more discussion. * Provide meaningful subject of a PR so that it could become commit message. * Provide good description, context and link to issues which are resolved. * Create examples of new funtionality. We keep them in testcases/ dir, and they are all executed as a part of CI. This is part of our documentation and verification. We encourage you to use existing test setup (helm deployed services) but suggestions for change or adding new ones are ok. * It would be perfect to write unit tests for what you are adding. It is not always needed for simple k8s object getters, but are mandatory for more complex logic. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 DevOps Spiral Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # KubeLibrary [![CircleCI Build Status](https://circleci.com/gh/devopsspiral/KubeLibrary.svg?style=shield)](https://circleci.com/gh/devopsspiral/KubeLibrary)[![PyPI](https://img.shields.io/pypi/v/robotframework-kubelibrary)](https://pypi.org/project/robotframework-kubelibrary/)[![PyPi downloads](https://img.shields.io/pypi/dm/robotframework-kubelibrary.svg)](https://pypi.python.org/pypi/robotframework-kubelibrary)[![GitHub License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/devopsspiral/k3d-orb/master/LICENSE)[![Slack](https://img.shields.io/badge/slack-robotframework%2F%23kubernetes-blue)](https://robotframework.slack.com/archives/C017AKKS06R) RobotFramework library for testing Kubernetes cluster ## Quick start ``` # install library itself pip install robotframework-kubelibrary # export KUBECONFIG export KUBECONFIG=~/.kube/config # run example tests pip install robotframework-requests git clone https://github.com/devopsspiral/KubeLibrary.git cd KubeLibrary robot -e prerelease testcases ``` ## Documentation [Library docs](http://devopsspiral.com/KubeLibrary/) ## Example testcase ``` testcases/system_smoke.robot *** Settings *** (1)Resource ./system_smoke_kw.robot *** Variables *** (2)${KUBELET_VERSION} %{KUBELET_VERSION} ${NUM_NODES} 2 ${NUM_WORKERS} 1 *** Test Cases *** (3)Pods in kube-system are ok (4) [Documentation] Test if all pods in kube-system initiated correctly and are running or succeeded (5) [Tags] cluster smoke (6) Given kubernetes API responds (7) When getting all pods names in "kube-system" (8) Then all pods in "kube-system" are running or succeeded ``` 1 - keyword definitions in separate file relative to testcase file 2 - defining local variable taking value from environment variable 3 - testcase definition 4 - Documentation/comments 5 - Tags, you can include (-i) and exclude (-e) tests by tag. 6(7,8) - Given, When, Then clause. It is only way of organizing your test steps, given, when, then are just omitted, real keywords definition needs to match 'kubernetes API responds', 'getting all pods names in ...' etc.(see testcases/system_smoke_kw.robot) 7 - kube-system in quotes is treated as parameter for 'getting all pods names in ...' keyword. More examples in testcases/ directory. To see all the tests passing execute below commands. ### Cluster Tests ``` # run cluster tests robot -i cluster -e prerelease testcases/ ``` ### Grafana Tests ``` helm repo add grafana https://grafana.github.io/helm-charts helm repo update helm install grafana grafana/grafana -f testcases/grafana/values.yaml # run grafana tests export KLIB_POD_PATTERN='grafana.*' export KLIB_POD_ANNOTATIONS='{"kubelibrary":"testing"}' export KLIB_POD_NAMESPACE=default robot -i grafana -e prerelease testcases/ ``` ### Other Tests These tests require the kubelib-test helm-chart to be installed in your test cluster. ``` # run other library tests export KLIB_POD_PATTERN='busybox.*' export KLIB_POD_NAMESPACE=kubelib-tests export KLIB_POD_LABELS='job-name=busybox-job' kubectl create namespace $KLIB_POD_NAMESPACE kubectl label namespaces kubelib-tests test=test helm install kubelib-test ./test-objects-chart -n $KLIB_POD_NAMESPACE robot -i other -e prerelease testcases/ ``` ### Multi Cluster Tests These tests require more than one cluster and utilize [KinD](https://kind.sigs.k8s.io/) as a setup. [Download KinD and install it.](https://kind.sigs.k8s.io/docs/user/quick-start/) ``` # Create Test Cluster 1 kind create cluster --kubeconfig ./cluster1-conf --name kind-cluster-1 # Create namespace in Test Cluster 1 kubectl create namespace test-ns-1 --context kind-kind-cluster-1 --kubeconfig ./cluster1-conf # For bearer token auth kubectl apply -f testcases/reload-config/sa.yaml MYSA_TOKEN_SECRET=$(kubectl get sa mysa -o jsonpath="{.secrets[0].name}") export K8S_TOKEN=$(kubectl get secret $MYSA_TOKEN_SECRET --template={{.data.token}} | base64 -d) kubectl get secret $MYSA_TOKEN_SECRET -o jsonpath="{.data.ca\.crt}" | base64 -d > ca.crt export K8S_API_URL=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}') export K8S_CA_CRT=./ca.crt # Create Test Cluster 2 kind create cluster --kubeconfig ./cluster2-conf --name kind-cluster-2 # Create namespace in Test Cluster 2 kubectl create namespace test-ns-2 --context kind-kind-cluster-2 --kubeconfig ./cluster2-conf robot -i reload-config -e prerelease testcases/ # Clean up kind delete cluster --name kind-cluster-1 kind delete cluster --name kind-cluster-2 ``` ## Keywords documentation Keywords documentation can be found in docs/. ## Proxy configuration To access cluster via proxy set `http_proxy` or `HTTP_PROXY` environment variable. In similar way you can set `no_proxy` or `NO_PROXY` variable to specify hosts that should be excluded from proxying. **IMPORTANT:** Lowercase environment variables have higher priority than uppercase ## Further reading [DevOps spiral article on KubeLibrary](https://devopsspiral.com/articles/k8s/robotframework-kubelibrary/) [KubeLibrary: Testing Kubernetes with RobotFramework | Humanitec](https://humanitec.com/blog/kubelibrary-testing-kubernetes-with-robotframework) [RobotFramework User Guide](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html) ## Development ``` # clone repo git clone https://github.com/devopsspiral/KubeLibrary.git cd KubeLibrary # create virtualenv virtualenv .venv . .venv/bin/activate pip install -r requirements-dev.txt ``` Create keyword and test file, import KubeLibrary using below to point to library under development. ``` *** Settings *** Library ../src/KubeLibrary/KubeLibrary.py ``` For development cluster you can use k3s/k3d as described in [DevOps spiral article on K3d and skaffold](https://devopsspiral.com/articles/k8s/k3d-skaffold/). ### Generate docs ``` ( # To generate keyword documentation a connection # to a cluster is not necessary. Skip to load a # cluster configuration. # # Set the variable local for the libdoc call only export INIT_FOR_LIBDOC_ONLY=1 python -m robot.libdoc src/KubeLibrary docs/index.html ) ``` ================================================ FILE: docs/index.html ================================================

Opening library documentation failed

  • Verify that you have JavaScript enabled in your browser.
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
================================================ FILE: requirements-dev.txt ================================================ -r requirements.txt mock flake8 coverage robotframework-requests ================================================ FILE: requirements.txt ================================================ google-auth>=2.5.0 kubernetes>=21.7.0 robotframework>=3.2.2 urllib3-mock>=0.3.3 ================================================ FILE: setup.py ================================================ from pkg_resources import parse_requirements from pathlib import Path from setuptools import setup exec(open("src/KubeLibrary/version.py").read()) with open("README.md", "r") as fh: long_description = fh.read() with Path("requirements.txt").open() as requirements: install_requires = [ str(requirement) for requirement in parse_requirements(requirements) ] setup( name="robotframework-kubelibrary", version=version, author="Michał Wcisło", author_email="mwcislo999@gmail.com", description="Kubernetes library for Robot Framework", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/devopsspiral/KubeLibrary", license="MIT", packages=["KubeLibrary"], classifiers=[ "Development Status :: 2 - Pre-Alpha", "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development :: Testing", ], keywords="robotframework testing test automation kubernetes", python_requires='>=3.6', package_dir={'': 'src'}, install_requires=install_requires, ) ================================================ FILE: src/KubeLibrary/KubeLibrary.py ================================================ import ast import json import re import ssl import urllib3 from os import environ from kubernetes import client, config, dynamic, stream from robot.api import logger from robot.api.deco import library from string import digits, ascii_lowercase from random import choices from KubeLibrary.exceptions import BearerTokenWithPrefixException from KubeLibrary.version import version # supressing SSL warnings when using self-signed certs urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class DynamicClient(dynamic.DynamicClient): @property def api_client(self): return self.client @library(scope="GLOBAL", version=version, auto_keywords=True) class KubeLibrary: """KubeLibrary is a Robot Framework test library for Kubernetes. The approach taken by this library is to provide easy to access kubernetes objects representation that can be then accessed to define highlevel keywords for tests. = Kubeconfigs = By default ~/.kube/config is used. Kubeconfig location can also be passed by setting KUBECONFIG environment variable or as Library argument. | ***** Settings ***** | Library KubeLibrary /path/to/kubeconfig = Context = By default current context from kubeconfig is used. Setting multiple contexts in different test suites allows working on multiple clusters. | ***** Settings ***** | Library KubeLibrary context=k3d-k3d-cluster2 = Bearer token authentication = It is possible to authenticate using bearer token by passing API url, bearer token and optionally CA certificate. | ***** Settings ***** | Library KubeLibrary api_url=%{K8S_API_URL} bearer_token=%{K8S_TOKEN} ca_cert=%{K8S_CA_CRT} = In cluster execution = If tests are supposed to be executed from within cluster, KubeLibrary can be configured to use standard token authentication. Just set incluster parameter to True. = Auth methods precedence = If enabled, auth methods takes precedence in following order: 1. Incluster 2. Bearer Token 3. Kubeconfig | ***** Settings ***** | Library KubeLibrary None True """ def __init__(self, kube_config=None, context=None, api_url=None, bearer_token=None, ca_cert=None, incluster=False, cert_validation=True): """KubeLibrary can be configured with several optional arguments. - ``kube_config``: Path pointing to kubeconfig of target Kubernetes cluster. - ``context``: Active context. If None current_context from kubeconfig is used. - ``api_url``: K8s API url, used for bearer token authenticaiton. - ``bearer_token``: Bearer token, used for bearer token authenticaiton. Do not include 'Bearer ' prefix. - ``ca_cert``: Optional CA certificate file path, used for bearer token authenticaiton. - ``incuster``: Default False. Indicates if used from within k8s cluster. Overrides kubeconfig. - ``cert_validation``: Default True. Can be set to False for self-signed certificates. Environment variables: - INIT_FOR_LIBDOC_ONLY: Set to '1' to generate keyword documentation and skip to load a kube config.. """ if "1" == environ.get('INIT_FOR_LIBDOC_ONLY', "0"): return self.reload_config(kube_config=kube_config, context=context, api_url=api_url, bearer_token=bearer_token, ca_cert=ca_cert, incluster=incluster, cert_validation=cert_validation) @staticmethod def get_proxy(): return environ.get('https_proxy') or environ.get('HTTPS_PROXY') or environ.get('http_proxy') or environ.get( 'HTTP_PROXY') @staticmethod def get_no_proxy(): return environ.get('no_proxy') or environ.get('NO_PROXY') @staticmethod def generate_alphanumeric_str(size): """Generates a random alphanumeric string with given size. Returns a string. - ``size``: Desired size of the output string """ return "".join(choices(ascii_lowercase + digits, k=size)) @staticmethod def evaluate_callable_from_k8s_client(attr_name, *args, **kwargs): """Evaluates a callable from kubernetes client. Returns the output of the client callable. - ``attr_name``: Callable name - ``*args``: Positional arguments for argument forwarding - ``**kwargs``: Keyword arguments for argument forwarding """ attr = getattr(client, attr_name, None) assert callable(attr), f"kubernetes.client does not contain {attr_name}!" return attr(*args, **kwargs) def get_dynamic_resource(self, api_version, kind): """Returns a dynamic resource based on the provided api version and kind. - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource """ return self.dynamic.resources.get(api_version=api_version, kind=kind) def get(self, api_version, kind, **kwargs): """Retrieves resource instances based on the provided parameters. Can be optionally given a ``namespace``, ``name``, ``label_selector``, ``body`` and ``field_selector``. Returns a resource list. - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource - ``**kwargs``: Keyword arguments for argument forwarding """ resource = self.get_dynamic_resource(api_version, kind) return resource.get(**kwargs) def create(self, api_version, kind, **kwargs): """Creates resource instances based on the provided configuration. If the resource is namespaced (ie, not cluster-level), then one of ``namespace``, ``label_selector``, or ``field_selector`` is required. If the resource is cluster-level, then one of ``name``, ``label_selector``, or ``field_selector`` is required. Can be optionally given a kubernetes manifest (``body``) which respects the above considerations. Returns created object - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource - ``**kwargs``: Keyword arguments for argument forwarding """ resource = self.get_dynamic_resource(api_version, kind) ret = resource.create(**kwargs) return ret def delete(self, api_version, kind, **kwargs): """Deletes resource instances based on the provided configuration. Can be optionally given a ``namespace``, ``name``, ``label_selector``, ``body`` and ``field_selector``. - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource - ``**kwargs``: Keyword arguments for argument forwarding """ resource = self.get_dynamic_resource(api_version, kind) resource.delete(**kwargs) def patch(self, api_version, kind, **kwargs): """Patches resource instances based on the provided parameters. Can be optionally given a ``namespace``, ``name``, ``label_selector``, ``body`` and ``field_selector``. - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource - ``**kwargs``: Keyword arguments for argument forwarding """ resource = self.get_dynamic_resource(api_version, kind) resource.patch(**kwargs) def replace(self, api_version, kind, **kwargs): """Replaces resource instances based on the provided parameters. Can be optionally given a ``namespace``, ``name``, ``label_selector``, ``body`` and ``field_selector``. - ``api_version``: Api version of the desired kubernetes resource - ``kind``: Kind of the desired kubernetes resource - ``**kwargs``: Keyword arguments for argument forwarding """ resource = self.get_dynamic_resource(api_version, kind) resource.replace(**kwargs) def reload_config(self, kube_config=None, context=None, api_url=None, bearer_token=None, ca_cert=None, incluster=False, cert_validation=True): """Reload the KubeLibrary to be configured with different optional arguments. This can be used to connect to a different cluster during the same test. - ``kube_config``: Path pointing to kubeconfig of target Kubernetes cluster. - ``context``: Active context. If None current_context from kubeconfig is used. - ``api_url``: K8s API url, used for bearer token authenticaiton. - ``bearer_token``: Bearer token, used for bearer token authenticaiton. Do not include 'Bearer ' prefix. - ``ca_cert``: Optional CA certificate file path, used for bearer token authenticaiton. - ``incuster``: Default False. Indicates if used from within k8s cluster. Overrides kubeconfig. - ``cert_validation``: Default True. Can be set to False for self-signed certificates. Environment variables: - HTTP_PROXY: Proxy URL """ self.api_client = None self.cert_validation = cert_validation if incluster: try: config.load_incluster_config() except config.config_exception.ConfigException as e: logger.error('Are you sure tests are executed from within k8s cluster?') raise e elif api_url and bearer_token: if bearer_token.startswith('Bearer '): raise BearerTokenWithPrefixException configuration = client.Configuration.get_default_copy() configuration.api_key["authorization"] = bearer_token configuration.api_key_prefix['authorization'] = 'Bearer' configuration.host = api_url configuration.ssl_ca_cert = ca_cert self.api_client = client.ApiClient(configuration) else: try: config.load_kube_config(kube_config, context) except TypeError: logger.error('Neither KUBECONFIG nor ~/.kube/config available.') if not self.api_client: self.api_client = client.ApiClient(configuration=client.Configuration.get_default_copy()) self._add_api('v1', client.CoreV1Api) self._add_api('networkingv1api', client.NetworkingV1Api) self._add_api('batchv1', client.BatchV1Api) self._add_api('appsv1', client.AppsV1Api) # self._add_api('batchv1_beta1', client.BatchV1Api) self._add_api('custom_object', client.CustomObjectsApi) self._add_api('rbac_authv1_api', client.RbacAuthorizationV1Api) self._add_api('autoscalingv1', client.AutoscalingV1Api) self._add_api('dynamic', DynamicClient) def _add_api(self, reference, class_name): self.__dict__[reference] = class_name(self.api_client) if not self.cert_validation: self.__dict__[reference].api_client.rest_client.pool_manager.connection_pool_kw['cert_reqs'] = ssl.CERT_NONE def k8s_api_ping(self): """Performs GET on /api/v1/ for simple check of API availability. Returns tuple of (response data, response status, response headers). Can be used as prerequisite in tests. """ path_params = {} query_params = [] header_params = {} auth_settings = ['BearerToken'] resp = self.v1.api_client.call_api('/api/v1/', 'GET', path_params, query_params, header_params, response_type='str', auth_settings=auth_settings, async_req=False, _return_http_data_only=False) return resp def k8s_version(self): """Performs GET on /version to show the k8s cluster version. Returns a dict of kubernetes version information, similar to `kubectl version`. """ path_params = {} query_params = [] header_params = {} auth_settings = ['BearerToken'] resp = self.v1.api_client.call_api('/version/', 'GET', path_params, query_params, header_params, response_type='str', auth_settings=auth_settings, async_req=False, _return_http_data_only=False) version = ast.literal_eval(resp[0]) return version def list_namespace(self, label_selector=""): """Lists available namespaces. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of namespaces. """ ret = self.v1.list_namespace(watch=False, label_selector=label_selector) return ret.items def get_namespaces(self, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespace. Gets a list of available namespaces. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of namespaces names. """ return self.filter_names(self.list_namespace(label_selector=label_selector)) def get_healthy_nodes_count(self, label_selector=""): """Counts node with KubeletReady and status True. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Can be used to check number of healthy nodes. Can be used as prerequisite in tests. """ ret = self.v1.list_node(watch=False, label_selector=label_selector) healthy_nods = [] for item in ret.items: for condition in item.status.conditions: if condition.reason == 'KubeletReady' and condition.status == 'True': healthy_nods.append(item.metadata.name) return len(healthy_nods) def get_pod_names_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_pod_by_pattern. Gets pod name matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``name_pattern``: Pod name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_pod(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern + '.*') return [item.metadata.name for item in ret.items if r.match(item.metadata.name)] def list_namespaced_pod_by_pattern(self, name_pattern, namespace, label_selector=""): """List pods matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of pods. - ``name_pattern``: Pod name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_pod(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) pods = [item for item in ret.items if r.match(item.metadata.name)] return pods def get_pods_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_pod_by_pattern. Gets pods matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of pods. - ``name_pattern``: Pod name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_pod(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) pods = [item for item in ret.items if r.match(item.metadata.name)] return pods def read_namespaced_pod_log(self, name, namespace, container, since_seconds=None): """Gets container logs of given pod in given namespace. Can be optionally filtered by time in seconds. e.g. since_seconds=1000. Returns logs. - ``name``: Pod name to check - ``namespace``: Namespace to check - ``container``: Container to check """ pod_logs = self.v1.read_namespaced_pod_log(name=name, namespace=namespace, container=container, follow=False, since_seconds=since_seconds) return pod_logs def get_pod_logs(self, name, namespace, container): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_pod_log. Gets container logs of given pod in given namespace. Returns logs. - ``name``: Pod name to check - ``namespace``: Namespace to check - ``container``: Container to check """ pod_logs = self.v1.read_namespaced_pod_log(name=name, namespace=namespace, container=container, follow=False) return pod_logs def list_namespaced_config_map_by_pattern(self, name_pattern, namespace, label_selector=""): """Lists configmaps matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of configmaps. - ``name_pattern``: configmap name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_config_map(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) configmaps = [item for item in ret.items if r.match(item.metadata.name)] return configmaps def get_configmaps_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_config_map_by_pattern. Gets configmaps matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of configmaps. - ``name_pattern``: configmap name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_config_map(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) configmaps = [item for item in ret.items if r.match(item.metadata.name)] return configmaps def list_namespaced_service_account_by_pattern(self, name_pattern, namespace, label_selector=""): """Lists service accounts matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of service accounts. - ``name_pattern``: Service Account name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_service_account(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) service_accounts = [item for item in ret.items if r.match(item.metadata.name)] return service_accounts def get_service_accounts_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_service_account_by_pattern. Gets service accounts matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of service accounts. - ``name_pattern``: Service Account name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_service_account(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) service_accounts = [item for item in ret.items if r.match(item.metadata.name)] return service_accounts def list_namespaced_deployment_by_pattern(self, name_pattern, namespace, label_selector=""): """Gets deployments matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of deployments. - ``name_pattern``: deployment name pattern to check - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_deployment(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) deployments = [item for item in ret.items if r.match(item.metadata.name)] return deployments def get_deployments_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_deployment_by_pattern. Gets deployments matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of deployments. - ``name_pattern``: deployment name pattern to check - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_deployment(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) deployments = [item for item in ret.items if r.match(item.metadata.name)] return deployments def list_namespaced_replica_set_by_pattern(self, name_pattern, namespace, label_selector=""): """Lists replicasets matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of replicasets. - ``name_pattern``: replicaset name pattern to check - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_replica_set(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) replicasets = [item for item in ret.items if r.match(item.metadata.name)] return replicasets def get_replicasets_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_replica_set_by_pattern. Gets replicasets matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of replicasets. - ``name_pattern``: replicaset name pattern to check - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_replica_set(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) replicasets = [item for item in ret.items if r.match(item.metadata.name)] return replicasets def list_namespaced_job_by_pattern(self, name_pattern, namespace, label_selector=""): """Gets jobs matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of jobs. - ``name_pattern``: job name pattern to check - ``namespace``: Namespace to check """ ret = self.batchv1.list_namespaced_job(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) jobs = [item for item in ret.items if r.match(item.metadata.name)] return jobs def get_jobs_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_job_by_pattern. Gets jobs matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of jobs. - ``name_pattern``: job name pattern to check - ``namespace``: Namespace to check """ ret = self.batchv1.list_namespaced_job(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) jobs = [item for item in ret.items if r.match(item.metadata.name)] return jobs def list_namespaced_secret_by_pattern(self, name_pattern, namespace, label_selector=""): """Lists secrets matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of secrets. - ``name_pattern``: secret name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_secret(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) secrets = [item for item in ret.items if r.match(item.metadata.name)] return secrets def get_secrets_in_namespace(self, name_pattern, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_secret_by_pattern. Gets secrets matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of secrets. - ``name_pattern``: secret name pattern to check - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_secret(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) secrets = [item for item in ret.items if r.match(item.metadata.name)] return secrets def get_namespaced_pod_exec(self, name, namespace, argv_cmd, container=None): """Exec command on selected container for POD. Returns command stdout/stderr - ``name``: pod name - ``namespace``: namespace to check - ``argv_cmd``: command to be executed using argv syntax: ["/bin/sh", "-c", "ls"] it do not use shell as default! - ``container``: container on which we run exec, default: None """ if not isinstance(argv_cmd, list) or not len(argv_cmd): raise TypeError( f"argv_cmd parameter should be a list and contains values like [\"/bin/bash\", \"-c\", \"ls\"] " f"not {argv_cmd}") if not container: return stream.stream(self.v1.connect_get_namespaced_pod_exec, name, namespace, command=argv_cmd, stderr=True, stdin=True, stdout=True, tty=False).strip() else: return stream.stream(self.v1.connect_get_namespaced_pod_exec, name, namespace, container=container, command=argv_cmd, stderr=True, stdin=True, stdout=True, tty=False).strip() def filter_names(self, objects): """Filter .metadata.name for list of k8s objects. Returns list of strings. - ``objects``: List of k8s objects """ return [obj.metadata.name for obj in objects] def filter_by_key(self, objects, key, match): """Filter object with key matching value for list of k8s objects. Returns list of objects. - ``objects``: List of k8s objects - ``key``: Key to match - ``match``: Value of the key based on which objects will be included """ return [obj for obj in objects if getattr(obj, key) == match] def filter_deployments_names(self, deployments): """*DEPRECATED* Will be removed in v1.0.0. See examples in TBD. Returns list of strings. - ``deployments``: List of deployments objects """ return self.filter_names(deployments) def filter_replicasets_names(self, replicasets): """*DEPRECATED* Will be removed in v1.0.0. See examples in TBD. Returns list of strings. - ``replicasets``: List of replicasets objects """ return self.filter_names(replicasets) def filter_pods_names(self, pods): """*DEPRECATED* Will be removed in v1.0.0. See examples in TBD. Filter pod names for list of pods. Returns list of strings. - ``pods``: List of pods objects """ return self.filter_names(pods) def filter_service_accounts_names(self, service_accounts): """*DEPRECATED* Will be removed in v1.0.0. See examples in TBD. Filter service accounts names for list of service accounts. Returns list of strings. - ``service_accounts``: List of service accounts objects """ return self.filter_names(service_accounts) def filter_configmap_names(self, configmaps): """*DEPRECATED* Will be removed in v1.0.0. See examples in TBD. Filter configmap names for list of configmaps. Returns list of strings. - ``configmaps``: List of configmap objects """ return self.filter_names(configmaps) def filter_endpoints_names(self, endpoints): """Filter endpoints names for list of endpoints. Returns list of strings. - ``endpoints``: List of endpoint objects """ return self.filter_names(endpoints.items) @staticmethod def filter_pods_containers_by_name(pods, name_pattern): """Filters pods containers by name for given list of pods. Returns lists of containers (flattens). - ``pods``: List of pods objects """ containers = [] r = re.compile(name_pattern) for pod in pods: for container in pod.spec.containers: if r.match(container.name): containers.append(container) return containers @staticmethod def filter_containers_images(containers): """Filters container images for given lists of containers. Returns list of images. - ``containers``: List of containers """ return [container.image for container in containers] @staticmethod def filter_containers_resources(containers): """Filters container resources for given lists of containers. Returns list of resources. - ``containers``: List of containers """ return [container.resources for container in containers] @staticmethod def filter_pods_containers_statuses_by_name(pods, name_pattern): """Filters pods containers statuses by container name for given list of pods. Returns lists of containers statuses. - ``pods``: List of pods objects """ container_statuses = [] r = re.compile(name_pattern) for pod in pods: for container_status in pod.status.container_statuses: if r.match(container_status.name): container_statuses.append(container_status) return container_statuses def read_namespaced_pod_status(self, name, namespace): """Reads pod status in given namespace. - ``name``: Name of pod. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_pod_status(name, namespace) return ret.status def get_pod_status_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_pod_status. - ``name``: Name of pod. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_pod_status(name, namespace) return ret.status.phase @staticmethod def assert_pod_has_labels(pod, labels_json): """Assert pod has labels. Returns True/False - ``pod``: Pod object. - ``labels_json``: JSON representing labels """ try: labels = json.loads(labels_json) for k, v in labels.items(): if pod.metadata.labels and k in pod.metadata.labels: if pod.metadata.labels[k] != v: logger.error(f'Label "{k}" value "{v}" not matching actual "{pod.metadata.labels[k]}"') return False else: logger.error(f'Label "{k}" not found in actual') return False return True except json.JSONDecodeError: logger.error(f'Failed parsing Pod Labels JSON:{labels_json}') return False @staticmethod def assert_pod_has_annotations(pod, annotations_json): """Assert pod has annotations. Returns True/False - ``pod``: Pod object. - ``annotations_json``: JSON representing annotations """ try: annotations = json.loads(annotations_json) for k, v in annotations.items(): if pod.metadata.annotations and k in pod.metadata.annotations: if pod.metadata.annotations[k] != v: logger.error( f'Annotation "{k}" value "{v}" not matching actual "{pod.metadata.annotations[k]}"') return False else: logger.error(f'Annotation "{k}" not found in actual') return False return True except json.JSONDecodeError: logger.error(f'Failed parsing Pod Annotations JSON:{annotations_json}') return False @staticmethod def assert_container_has_env_vars(container, env_vars_json): """Assert container has env vars. Returns True/False - ``container``: Container object. - ``env_var_json``: JSON representing env vars i.e.: {"EXAMPLE_VAR": "examplevalue"} """ try: env_vars = json.loads(env_vars_json) for k, v in env_vars.items(): found = False for ev in container.env: if k == ev.name and v == ev.value: found = True break elif k == ev.name and v != ev.value: logger.error(f'Env var "{k}" value "{v}" not matching actual "{ev.value}"') return False if not found: logger.error(f'Env var "{k}" not found in actual') return False return True except json.JSONDecodeError: logger.error(f'Failed parsing Container Env Var JSON:{env_vars_json}') return False def list_namespaced_service(self, namespace, label_selector=""): """Gets services in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_service(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def get_services_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_service. Gets services in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_service(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_service(self, name, namespace): """Gets service details in given namespace. Returns Service object representation. Can be accessed using | Should Be Equal As integers | ${service_details.spec.ports[0].port} | 8080 | - ``name``: Name of service. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_service(name, namespace) return ret def get_service_details_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_service. Gets service details in given namespace. Returns Service object representation. Can be accessed using | Should Be Equal As integers | ${service_details.spec.ports[0].port} | 8080 | - ``name``: Name of service. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_service(name, namespace) return ret def list_namespaced_horizontal_pod_autoscaler(self, namespace, label_selector=""): """Gets Horizontal Pod Autoscalers in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.autoscalingv1.list_namespaced_horizontal_pod_autoscaler(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def get_hpas_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_horizontal_pod_autoscaler. Gets Horizontal Pod Autoscalers in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.autoscalingv1.list_namespaced_horizontal_pod_autoscaler(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_horizontal_pod_autoscaler(self, name, namespace): """Gets Horizontal Pod Autoscaler details in given namespace. Returns Horizontal Pod Autoscaler object representation. Can be accessed using | Should Be Equal As integers | ${hpa_details.spec.target_cpu_utilization_percentage} | 50 | - ``name``: Name of Horizontal Pod Autoscaler - ``namespace``: Namespace to check """ ret = self.autoscalingv1.read_namespaced_horizontal_pod_autoscaler(name, namespace) return ret def get_hpa_details_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_horizontal_pod_autoscaler. Gets Horizontal Pod Autoscaler details in given namespace. Returns Horizontal Pod Autoscaler object representation. Can be accessed using | Should Be Equal As integers | ${hpa_details.spec.target_cpu_utilization_percentage} | 50 | - ``name``: Name of Horizontal Pod Autoscaler - ``namespace``: Namespace to check """ ret = self.autoscalingv1.read_namespaced_horizontal_pod_autoscaler(name, namespace) return ret def read_namespaced_endpoints(self, name, namespace): """Gets endpoint details in given namespace. Returns Endpoint object representation. Can be accessed using | Should Match | ${endpoint_details.subsets[0].addresses[0].target_ref.name} | pod-name-123456 | - ``name``: Name of endpoint. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_endpoints(name, namespace) return ret def get_endpoints_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_endpoints. Gets endpoint details in given namespace. Returns Endpoint object representation. Can be accessed using | Should Match | ${endpoint_details.subsets[0].addresses[0].target_ref.name} | pod-name-123456 | - ``name``: Name of endpoint. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_endpoints(name, namespace) return ret def list_namespaced_persistent_volume_claim(self, namespace, label_selector=""): """Gets pvcs in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_persistent_volume_claim(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def list_namespaced_persistent_volume_claim_by_pattern(self, name_pattern, namespace, label_selector=""): """Gets pvcs in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check - ``name_pattern``: pvc name pattern to check """ ret = self.v1.list_namespaced_persistent_volume_claim(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) return [item for item in ret.items if r.match(item.metadata.name)] def list_namespaced_stateful_set(self, namespace, label_selector=""): """Lists statefulsets in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of statefulsets. - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_stateful_set(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def list_namespaced_stateful_set_by_pattern(self, name_pattern, namespace, label_selector=""): """Lists statefulsets matching pattern in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of statefulsets. - ``namespace``: Namespace to check - ``name_pattern``: statefulset name pattern to check """ ret = self.appsv1.list_namespaced_stateful_set(namespace, watch=False, label_selector=label_selector) r = re.compile(name_pattern) statefulsets = [item for item in ret.items if r.match(item.metadata.name)] return statefulsets def get_pvc_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_persistent_volume_claim. Gets pvcs in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.v1.list_namespaced_persistent_volume_claim(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_persistent_volume_claim(self, name, namespace): """Gets PVC details in given namespace. Returns PVC object representation. Can be accessed using | Should Be Equal As strings | ${pvc.status.capacity.storage} | 1Gi | - ``name``: Name of PVC. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_persistent_volume_claim(name, namespace) return ret def get_pvc_capacity(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_persistent_volume_claim. Gets PVC details in given namespace. Returns PVC object representation. Can be accessed using | Should Be Equal As strings | ${pvc.status.capacity.storage} | 1Gi | - ``name``: Name of PVC. - ``namespace``: Namespace to check """ ret = self.v1.read_namespaced_persistent_volume_claim(name, namespace) return ret def get_kubelet_version(self, label_selector=""): """Gets list of kubelet versions on each node. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. """ ret = self.v1.list_node(watch=False, label_selector=label_selector) return [item.status.node_info.kubelet_version for item in ret.items] def create_namespaced_service_account(self, namespace, body): """Creates service account in a namespace Returns created service account - ``body``: Service Account object. - ``namespace``: Namespace to check """ ret = self.v1.create_namespaced_service_account(namespace=namespace, body=body) return ret def create_service_account_in_namespace(self, namespace, body): """*DEPRECATED* Will be removed in v1.0.0. Use create_namespaced_service_account. Creates service account in a namespace Returns created service account - ``body``: Service Account object. - ``namespace``: Namespace to check """ ret = self.v1.create_namespaced_service_account(namespace=namespace, body=body) return ret def delete_namespaced_service_account(self, name, namespace): """Deletes service account in a namespace Returns V1status - ``name``: Service Account name - ``namespace``: Namespace to check """ ret = self.v1.delete_namespaced_service_account(name=name, namespace=namespace) return ret def delete_service_account_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use delete_namespaced_service_account. Deletes service account in a namespace Returns V1status - ``name``: Service Account name - ``namespace``: Namespace to check """ ret = self.v1.delete_namespaced_service_account(name=name, namespace=namespace) return ret def get_healthcheck(self, endpoint='/readyz', verbose=False): """Performs GET on /readyz or /livez for simple health check. Can be used to verify the readiness/current status of the API server Returns tuple of (response data, response status and response headers) - ``endpoint``: /readyz, /livez or induvidual endpoints like '/livez/etcd'. defaults to /readyz - ``verbose``: More detailed output. https://kubernetes.io/docs/reference/using-api/health-checks """ path_params = {} query_params = [] header_params = {} auth_settings = ['BearerToken'] if not (endpoint.startswith('/readyz') or endpoint.startswith('/livez')): raise RuntimeError(f'{endpoint} does not start with "/readyz" or "/livez"') endpoint = endpoint if not verbose else endpoint + '?verbose' resp = self.v1.api_client.call_api(endpoint, 'GET', path_params, query_params, header_params, response_type='str', auth_settings=auth_settings, async_req=False, _return_http_data_only=False) return resp def list_namespaced_ingress(self, namespace, label_selector=""): """Gets ingresses in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.networkingv1api.list_namespaced_ingress(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def get_ingresses_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_ingress. Gets ingresses in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.networkingv1api.list_namespaced_ingress(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_ingress(self, name, namespace): """Gets ingress details in given namespace. Returns Ingress object representation. Name of ingress. - ``namespace``: Namespace to check """ ret = self.networkingv1api.read_namespaced_ingress(name, namespace) return ret def get_ingress_details_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_ingress. Gets ingress details in given namespace. Returns Ingress object representation. Name of ingress. - ``namespace``: Namespace to check """ ret = self.networkingv1api.read_namespaced_ingress(name, namespace) return ret def list_namespaced_cron_job(self, namespace, label_selector=""): """Gets cron jobs in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.batchv1.list_namespaced_cron_job(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def get_cron_jobs_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_cron_job. Gets cron jobs in given namespace. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of strings. - ``namespace``: Namespace to check """ ret = self.batchv1.list_namespaced_cron_job(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_cron_job(self, name, namespace): """Gets cron job details in given namespace. Returns Cron job object representation. - ``name``: Name of cron job. - ``namespace``: Namespace to check """ ret = self.batchv1.read_namespaced_cron_job(name, namespace) return ret def get_cron_job_details_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_cron_job. Gets cron job details in given namespace. Returns Cron job object representation. - ``name``: Name of cron job. - ``namespace``: Namespace to check """ ret = self.batchv1.read_namespaced_cron_job(name, namespace) return ret def list_namespaced_daemon_set(self, namespace, label_selector=""): """Gets a list of available daemonsets. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of deaemonsets. - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_daemon_set(namespace, watch=False, label_selector=label_selector) return [item for item in ret.items] def get_daemonsets_in_namespace(self, namespace, label_selector=""): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_daemon_set. Gets a list of available daemonsets. Can be optionally filtered by label. e.g. label_selector=label_key=label_value Returns list of deaemonsets. - ``namespace``: Namespace to check """ ret = self.appsv1.list_namespaced_daemon_set(namespace, watch=False, label_selector=label_selector) return [item.metadata.name for item in ret.items] def read_namespaced_daemon_set(self, name, namespace): """Gets deamonset details in given namespace. Returns daemonset object representation. - ``name``: Name of the daemonset - ``namespace``: Namespace to check """ ret = self.appsv1.read_namespaced_daemon_set(name, namespace) return ret def get_daemonset_details_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use read_namespaced_daemon_set. Gets deamonset details in given namespace. Returns daemonset object representation. - ``name``: Name of the daemonset - ``namespace``: Namespace to check """ ret = self.appsv1.read_namespaced_daemon_set(name, namespace) return ret def list_cluster_role(self): """Gets a list of cluster_roles. Returns list of cluster_roles. """ ret = self.rbac_authv1_api.list_cluster_role(watch=False) return [item for item in ret.items] def get_cluster_roles(self): """*DEPRECATED* Will be removed in v1.0.0. Use list_cluster_role. Gets a list of cluster_roles. Returns list of cluster_roles. """ ret = self.rbac_authv1_api.list_cluster_role(watch=False) return [item.metadata.name for item in ret.items] def list_cluster_role_binding(self): """Gets a list of cluster_role_bindings. Returns list of cluster_role_bindings. """ ret = self.rbac_authv1_api.list_cluster_role_binding(watch=False) return [item for item in ret.items] def get_cluster_role_bindings(self): """*DEPRECATED* Will be removed in v1.0.0. Use list_cluster_role_binding. Gets a list of cluster_role_bindings. Returns list of cluster_role_bindings. """ ret = self.rbac_authv1_api.list_cluster_role_binding(watch=False) return [item.metadata.name for item in ret.items] def list_namespaced_role(self, namespace): """Gets roles in given namespace. Returns list of roles. - ``namespace``: Namespace to check """ ret = self.rbac_authv1_api.list_namespaced_role(namespace, watch=False) return [item for item in ret.items] def get_roles_in_namespace(self, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_role. Gets roles in given namespace. Returns list of roles. - ``namespace``: Namespace to check """ ret = self.rbac_authv1_api.list_namespaced_role(namespace, watch=False) return [item.metadata.name for item in ret.items] def list_namespaced_role_binding(self, namespace): """Gets role_bindings in given namespace. Returns list of role_bindings. - ``namespace``: Namespace to check """ ret = self.rbac_authv1_api.list_namespaced_role_binding(namespace, watch=False) return [item for item in ret.items] def get_role_bindings_in_namespace(self, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use list_namespaced_role_binding. Gets role_bindings in given namespace. Returns list of role_bindings. - ``namespace``: Namespace to check """ ret = self.rbac_authv1_api.list_namespaced_role_binding(namespace, watch=False) return [item.metadata.name for item in ret.items] def list_cluster_custom_object(self, group, version, plural): """Lists cluster level custom objects. Returns an object. - ``group``: API Group, e.g. 'k8s.cni.cncf.io' - ``version``: API version, e.g. 'v1' - ``plural``: e.g. 'network-attachment-definitions' As in ``GET /apis/{group}/{version}/{plural}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.list_cluster_custom_object(group, version, plural) def list_cluster_custom_objects(self, group, version, plural): """*DEPRECATED* Will be removed in v1.0.0. Use list_cluster_custom_object. Lists cluster level custom objects. Returns an object. - ``group``: API Group, e.g. 'k8s.cni.cncf.io' - ``version``: API version, e.g. 'v1' - ``plural``: e.g. 'network-attachment-definitions' As in ``GET /apis/{group}/{version}/{plural}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.list_cluster_custom_object(group, version, plural) def get_cluster_custom_object(self, group, version, plural, name): """Get cluster level custom object. Returns an object. - ``group``: API Group, e.g. 'scheduling.k8s.io' - ``version``: API version, e.g. 'v1' - ``plural``: e.g. 'priorityclasses' - ``name``: e.g. 'system-node-critical' As in ``GET /apis/{group}/{version}/{plural}/{name}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.get_cluster_custom_object(group, version, plural, name) def get_namespaced_custom_object(self, group, version, namespace, plural, name): """Get custom object in namespace. Returns an object. - ``group``: API Group, e.g. 'k8s.cni.cncf.io' - ``version``: API version, e.g. 'v1' - ``namespace``: Namespace, e.g. 'default' - ``plural``: e.g. 'network-attachment-definitions' - ``name``: e.g. 'my-network' As in ``GET /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.get_namespaced_custom_object(group, version, namespace, plural, name) def list_namespaced_custom_object(self, group, version, namespace, plural): """List custom objects in namespace. Returns an object. - ``group``: API Group, e.g. 'k8s.cni.cncf.io' - ``version``: API version, e.g. 'v1' - ``namespace``: Namespace, e.g. 'default' - ``plural``: e.g. 'network-attachment-definitions' As in ``GET /apis/{group}/{version}/namespaces/{namespace}/{plural}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.list_namespaced_custom_object(group, version, namespace, plural) def get_custom_object_in_namespace(self, group, version, namespace, plural, name): """*DEPRECATED* Will be removed in v1.0.0. Use get_namespaced_custom_object. Get custom object in namespace. Returns an object. - ``group``: API Group, e.g. 'k8s.cni.cncf.io' - ``version``: API version, e.g. 'v1' - ``namespace``: Namespace, e.g. 'default' - ``plural``: e.g. 'network-attachment-definitions' - ``name``: e.g. 'my-network' As in ``GET /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}`` https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md """ return self.custom_object.get_namespaced_custom_object(group, version, namespace, plural, name) def create_namespaced_cron_job(self, namespace, body): """Creates cron_job in a namespace Returns created cron_job - ``body``: Cron_job object. - ``namespace``: Namespace to check """ ret = self.batchv1.create_namespaced_cron_job(namespace=namespace, body=body) return ret def create_cron_job_in_namespace(self, namespace, body): """*DEPRECATED* Will be removed in v1.0.0. Use create_namespaced_cron_job. Creates cron_job in a namespace Returns created cron_job - ``body``: Cron_job object. - ``namespace``: Namespace to check """ ret = self.batchv1.create_namespaced_cron_job(namespace=namespace, body=body) return ret def delete_namespaced_cron_job(self, name, namespace): """Deletes cron_job in a namespace Returns V1 status - ``name``: Cron Job name - ``namespace``: Namespace to check """ ret = self.batchv1.delete_namespaced_cron_job(name=name, namespace=namespace) return ret def delete_cron_job_in_namespace(self, name, namespace): """*DEPRECATED* Will be removed in v1.0.0. Use delete_namespaced_cron_job. Deletes cron_job in a namespace Returns V1 status - ``name``: Cron Job name - ``namespace``: Namespace to check """ ret = self.batchv1.delete_namespaced_cron_job(name=name, namespace=namespace) return ret ================================================ FILE: src/KubeLibrary/__init__.py ================================================ from .KubeLibrary import KubeLibrary # noqa: F401 ================================================ FILE: src/KubeLibrary/exceptions.py ================================================ class BearerTokenWithPrefixException(Exception): ROBOT_SUPPRESS_NAME = True def __init__(self): super().__init__("Unnecessary 'Bearer ' prefix in token") pass ================================================ FILE: src/KubeLibrary/version.py ================================================ version = "0.8.10" ================================================ FILE: test/__init__.py ================================================ ================================================ FILE: test/resources/cluster_role.json ================================================ [ { "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRole", "metadata": { "creationTimestamp": "2021-03-16T12:06:59Z", "managedFields": [ { "apiVersion": "rbac.authorization.k8s.io/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:rules": {} }, "manager": "kubectl", "operation": "Update", "time": "2021-03-16T12:06:59Z" } ], "name": "secret-reader", "resourceVersion": "678", "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterroles/secret-reader", "uid": "8fe2238a-9132-4b5d-a1d6-8c68b7c6abf0" }, "rules": [ { "apiGroups": [ "" ], "resources": [ "secrets" ], "verbs": [ "get", "watch", "list" ] } ] } ] ================================================ FILE: test/resources/cluster_role_bind.json ================================================ [ { "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRoleBinding", "metadata": { "creationTimestamp": "2021-03-16T12:25:09Z", "managedFields": [ { "apiVersion": "rbac.authorization.k8s.io/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:roleRef": { "f:apiGroup": {}, "f:kind": {}, "f:name": {} }, "f:subjects": {} }, "manager": "kubectl", "operation": "Update", "time": "2021-03-16T12:25:09Z" } ], "name": "read-secrets-global", "resourceVersion": "3556", "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/read-secrets-global", "uid": "e31d1893-811b-4b63-8051-0c8ffba31140" }, "roleRef": { "apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "secret-reader" }, "subjects": [ { "apiGroup": "rbac.authorization.k8s.io", "kind": "Group", "name": "manager" } ] } ] ================================================ FILE: test/resources/configmap.json ================================================ [ { "apiVersion": "v1", "data": { "game.properties": "enemy.types=aliens,monsters\nplayer.maximum-lives=5 \n", "player_initial_lives": "3", "ui_properties_file_name": "user-interface.properties", "user-interface.properties": "color.good=purple\ncolor.bad=yellow\nallow.textmode=true \n" }, "kind": "ConfigMap", "metadata": { "creationTimestamp": "2021-03-17T13:06:44Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:data": { ".": {}, "f:game.properties": {}, "f:player_initial_lives": {}, "f:ui_properties_file_name": {}, "f:user-interface.properties": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-17T13:06:44Z" } ], "name": "game-demo", "namespace": "default", "resourceVersion": "10295", "selfLink": "/api/v1/namespaces/default/configmaps/game-demo", "uid": "78083cf6-4d1a-475a-abf8-0da3d21bb8a5" } } ] ================================================ FILE: test/resources/cronjob.json ================================================ [ { "apiVersion": "batch/v1beta1", "kind": "CronJob", "metadata": { "creationTimestamp": "2021-03-25T04:02:24Z", "managedFields": [ { "apiVersion": "batch/v1beta1", "fieldsType": "FieldsV1", "fieldsV1": { "f:spec": { "f:concurrencyPolicy": {}, "f:failedJobsHistoryLimit": {}, "f:jobTemplate": { "f:spec": { "f:template": { "f:spec": { "f:containers": { "k:{\"name\":\"hello\"}": { ".": {}, "f:command": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:resources": {}, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {} } }, "f:dnsPolicy": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {} } } } }, "f:schedule": {}, "f:successfulJobsHistoryLimit": {}, "f:suspend": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-25T04:02:24Z" } ], "name": "hello", "resourceVersion": "17206", "selfLink": "/apis/batch/v1beta1/namespaces/kubelib-tests/cronjobs/hello", "uid": "95049672-9fb9-4a23-ab69-046454a4104d" }, "spec": { "concurrencyPolicy": "Allow", "failedJobsHistoryLimit": 1, "jobTemplate": { "metadata": { "creationTimestamp": null }, "spec": { "template": { "metadata": { "creationTimestamp": null }, "spec": { "containers": [ { "command": [ "/bin/sh", "-c", "date; echo Hello from the Kubernetes cluster" ], "image": "busybox", "imagePullPolicy": "IfNotPresent", "name": "hello", "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } } }, "schedule": "*/1 * * * *", "successfulJobsHistoryLimit": 3, "suspend": false }, "status": {} } ] ================================================ FILE: test/resources/cronjob_details.json ================================================ { "apiVersion": "batch/v1beta1", "kind": "CronJob", "metadata": { "creationTimestamp": "2021-04-19T10:54:19Z", "labels": { "TestLabel": "mytestlabel" }, "managedFields": [ { "apiVersion": "batch/v1beta1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:labels": { ".": {}, "f:TestLabel": {} } }, "f:spec": { "f:concurrencyPolicy": {}, "f:failedJobsHistoryLimit": {}, "f:jobTemplate": { "f:spec": { "f:template": { "f:metadata": { "f:labels": { ".": {}, "f:TestLabel": {} } }, "f:spec": { "f:containers": { "k:{\"name\":\"hello\"}": { ".": {}, "f:args": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:resources": {}, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {} } }, "f:dnsPolicy": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {} } } } }, "f:schedule": {}, "f:successfulJobsHistoryLimit": {}, "f:suspend": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-04-19T10:54:19Z" } ], "name": "hello", "namespace": "default", "resourceVersion": "744", "selfLink": "/apis/batch/v1beta1/namespaces/default/cronjobs/hello", "uid": "a4030af5-39e0-4b8e-a6a1-65811afbbab7" }, "spec": { "concurrencyPolicy": "Allow", "failedJobsHistoryLimit": 1, "jobTemplate": { "metadata": { "creationTimestamp": null }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "TestLabel": "mytestlabel" } }, "spec": { "containers": [ { "args": [ "/bin/sh", "-c", "date; echo Hello from the Kubernetes cluster" ], "image": "busybox", "imagePullPolicy": "IfNotPresent", "name": "hello", "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } } }, "schedule": "*/1 * * * *", "successfulJobsHistoryLimit": 3, "suspend": false }, "status": {} } ================================================ FILE: test/resources/daemonset.json ================================================ [ { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "deprecated.daemonset.template.generation": "1" }, "creationTimestamp": "2021-03-25T04:00:10Z", "generation": 1, "labels": { "k8s-app": "fluentd-logging" }, "managedFields": [ { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:annotations": { ".": {}, "f:deprecated.daemonset.template.generation": {} }, "f:labels": { ".": {}, "f:k8s-app": {} } }, "f:spec": { "f:revisionHistoryLimit": {}, "f:selector": { "f:matchLabels": { ".": {}, "f:name": {} } }, "f:template": { "f:metadata": { "f:labels": { ".": {}, "f:name": {} } }, "f:spec": { "f:containers": { "k:{\"name\":\"fluentd-elasticsearch\"}": { ".": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:resources": { ".": {}, "f:limits": { ".": {}, "f:memory": {} }, "f:requests": { ".": {}, "f:cpu": {}, "f:memory": {} } }, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {}, "f:volumeMounts": { ".": {}, "k:{\"mountPath\":\"/var/lib/docker/containers\"}": { ".": {}, "f:mountPath": {}, "f:name": {}, "f:readOnly": {} }, "k:{\"mountPath\":\"/var/log\"}": { ".": {}, "f:mountPath": {}, "f:name": {} } } } }, "f:dnsPolicy": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {}, "f:tolerations": {}, "f:volumes": { ".": {}, "k:{\"name\":\"varlibdockercontainers\"}": { ".": {}, "f:hostPath": { ".": {}, "f:path": {}, "f:type": {} }, "f:name": {} }, "k:{\"name\":\"varlog\"}": { ".": {}, "f:hostPath": { ".": {}, "f:path": {}, "f:type": {} }, "f:name": {} } } } }, "f:updateStrategy": { "f:rollingUpdate": { ".": {}, "f:maxUnavailable": {} }, "f:type": {} } } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-25T04:00:10Z" }, { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:status": { "f:currentNumberScheduled": {}, "f:desiredNumberScheduled": {}, "f:numberAvailable": {}, "f:numberReady": {}, "f:observedGeneration": {}, "f:updatedNumberScheduled": {} } }, "manager": "kube-controller-manager", "operation": "Update", "time": "2021-03-25T04:00:22Z" } ], "name": "fluentd-elasticsearch", "resourceVersion": "16870", "selfLink": "/apis/apps/v1/namespaces/kubelib-tests/daemonsets/fluentd-elasticsearch", "uid": "09db9622-6111-402a-a9b9-d2ed8f0ee65c" }, "spec": { "revisionHistoryLimit": 10, "selector": { "matchLabels": { "name": "fluentd-elasticsearch" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "name": "fluentd-elasticsearch" } }, "spec": { "containers": [ { "image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2", "imagePullPolicy": "IfNotPresent", "name": "fluentd-elasticsearch", "resources": { "limits": { "memory": "200Mi" }, "requests": { "cpu": "100m", "memory": "200Mi" } }, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File", "volumeMounts": [ { "mountPath": "/var/log", "name": "varlog" }, { "mountPath": "/var/lib/docker/containers", "name": "varlibdockercontainers", "readOnly": true } ] } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30, "tolerations": [ { "effect": "NoSchedule", "key": "node-role.kubernetes.io/master" } ], "volumes": [ { "hostPath": { "path": "/var/log", "type": "" }, "name": "varlog" }, { "hostPath": { "path": "/var/lib/docker/containers", "type": "" }, "name": "varlibdockercontainers" } ] } }, "updateStrategy": { "rollingUpdate": { "maxUnavailable": 1 }, "type": "RollingUpdate" } }, "status": { "currentNumberScheduled": 2, "desiredNumberScheduled": 2, "numberAvailable": 2, "numberMisscheduled": 0, "numberReady": 2, "observedGeneration": 1, "updatedNumberScheduled": 2 } } ] ================================================ FILE: test/resources/daemonset_details.json ================================================ { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "deprecated.daemonset.template.generation": "1" }, "creationTimestamp": "2021-04-22T14:18:47Z", "generation": 1, "labels": { "TestLabel": "mytestlabel" }, "managedFields": [ { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:annotations": { ".": {}, "f:deprecated.daemonset.template.generation": {} }, "f:labels": { ".": {}, "f:TestLabel": {} } }, "f:spec": { "f:revisionHistoryLimit": {}, "f:selector": { "f:matchLabels": { ".": {}, "f:name": {} } }, "f:template": { "f:metadata": { "f:labels": { ".": {}, "f:name": {} } }, "f:spec": { "f:containers": { "k:{\"name\":\"fluentd-elasticsearch\"}": { ".": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:resources": { ".": {}, "f:limits": { ".": {}, "f:memory": {} }, "f:requests": { ".": {}, "f:cpu": {}, "f:memory": {} } }, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {}, "f:volumeMounts": { ".": {}, "k:{\"mountPath\":\"/var/lib/docker/containers\"}": { ".": {}, "f:mountPath": {}, "f:name": {}, "f:readOnly": {} }, "k:{\"mountPath\":\"/var/log\"}": { ".": {}, "f:mountPath": {}, "f:name": {} } } } }, "f:dnsPolicy": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {}, "f:tolerations": {}, "f:volumes": { ".": {}, "k:{\"name\":\"varlibdockercontainers\"}": { ".": {}, "f:hostPath": { ".": {}, "f:path": {}, "f:type": {} }, "f:name": {} }, "k:{\"name\":\"varlog\"}": { ".": {}, "f:hostPath": { ".": {}, "f:path": {}, "f:type": {} }, "f:name": {} } } } }, "f:updateStrategy": { "f:rollingUpdate": { ".": {}, "f:maxUnavailable": {} }, "f:type": {} } } }, "manager": "kubectl", "operation": "Update", "time": "2021-04-22T14:18:47Z" }, { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:status": { "f:currentNumberScheduled": {}, "f:desiredNumberScheduled": {}, "f:numberAvailable": {}, "f:numberReady": {}, "f:observedGeneration": {}, "f:updatedNumberScheduled": {} } }, "manager": "kube-controller-manager", "operation": "Update", "time": "2021-04-22T14:19:00Z" } ], "name": "fluentd-elasticsearch", "namespace": "default", "resourceVersion": "1498", "selfLink": "/apis/apps/v1/namespaces/default/daemonsets/fluentd-elasticsearch", "uid": "684cdcd7-28c3-4d52-ba26-3aea85895961" }, "spec": { "revisionHistoryLimit": 10, "selector": { "matchLabels": { "name": "fluentd-elasticsearch" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "name": "fluentd-elasticsearch" } }, "spec": { "containers": [ { "image": "quay.io/fluentd_elasticsearch/fluentd:v2.5.2", "imagePullPolicy": "IfNotPresent", "name": "fluentd-elasticsearch", "resources": { "limits": { "memory": "200Mi" }, "requests": { "cpu": "100m", "memory": "200Mi" } }, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File", "volumeMounts": [ { "mountPath": "/var/log", "name": "varlog" }, { "mountPath": "/var/lib/docker/containers", "name": "varlibdockercontainers", "readOnly": true } ] } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30, "tolerations": [ { "effect": "NoSchedule", "key": "node-role.kubernetes.io/master" } ], "volumes": [ { "hostPath": { "path": "/var/log", "type": "" }, "name": "varlog" }, { "hostPath": { "path": "/var/lib/docker/containers", "type": "" }, "name": "varlibdockercontainers" } ] } }, "updateStrategy": { "rollingUpdate": { "maxUnavailable": 1 }, "type": "RollingUpdate" } }, "status": { "currentNumberScheduled": 2, "desiredNumberScheduled": 2, "numberAvailable": 2, "numberMisscheduled": 0, "numberReady": 2, "observedGeneration": 1, "updatedNumberScheduled": 2 } } ================================================ FILE: test/resources/deployment.json ================================================ [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "deployment.kubernetes.io/revision": "1" }, "creationTimestamp": "2021-03-21T14:10:07Z", "generation": 1, "labels": { "app": "nginx" }, "managedFields": [ { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:labels": { ".": {}, "f:app": {} } }, "f:spec": { "f:progressDeadlineSeconds": {}, "f:replicas": {}, "f:revisionHistoryLimit": {}, "f:selector": { "f:matchLabels": { ".": {}, "f:app": {} } }, "f:strategy": { "f:rollingUpdate": { ".": {}, "f:maxSurge": {}, "f:maxUnavailable": {} }, "f:type": {} }, "f:template": { "f:metadata": { "f:labels": { ".": {}, "f:app": {} } }, "f:spec": { "f:containers": { "k:{\"name\":\"nginx\"}": { ".": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:ports": { ".": {}, "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { ".": {}, "f:containerPort": {}, "f:protocol": {} } }, "f:resources": {}, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {} } }, "f:dnsPolicy": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {} } } } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-21T14:10:07Z" }, { "apiVersion": "apps/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:annotations": { ".": {}, "f:deployment.kubernetes.io/revision": {} } }, "f:status": { "f:availableReplicas": {}, "f:conditions": { ".": {}, "k:{\"type\":\"Available\"}": { ".": {}, "f:lastTransitionTime": {}, "f:lastUpdateTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} }, "k:{\"type\":\"Progressing\"}": { ".": {}, "f:lastTransitionTime": {}, "f:lastUpdateTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} } }, "f:observedGeneration": {}, "f:readyReplicas": {}, "f:replicas": {}, "f:updatedReplicas": {} } }, "manager": "kube-controller-manager", "operation": "Update", "time": "2021-03-21T14:10:15Z" } ], "name": "nginx-deployment", "namespace": "default", "resourceVersion": "5101", "selfLink": "/apis/apps/v1/namespaces/default/deployments/nginx-deployment", "uid": "7c69ea94-eb4e-4246-90f2-9e9d20964e66" }, "spec": { "progressDeadlineSeconds": 600, "replicas": 3, "revisionHistoryLimit": 10, "selector": { "matchLabels": { "app": "nginx" } }, "strategy": { "rollingUpdate": { "maxSurge": "25%", "maxUnavailable": "25%" }, "type": "RollingUpdate" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "app": "nginx" } }, "spec": { "containers": [ { "image": "nginx:1.14.2", "imagePullPolicy": "IfNotPresent", "name": "nginx", "ports": [ { "containerPort": 80, "protocol": "TCP" } ], "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "availableReplicas": 3, "conditions": [ { "lastTransitionTime": "2021-03-21T14:10:15Z", "lastUpdateTime": "2021-03-21T14:10:15Z", "message": "Deployment has minimum availability.", "reason": "MinimumReplicasAvailable", "status": "True", "type": "Available" }, { "lastTransitionTime": "2021-03-21T14:10:07Z", "lastUpdateTime": "2021-03-21T14:10:15Z", "message": "ReplicaSet \"nginx-deployment-6b474476c4\" has successfully progressed.", "reason": "NewReplicaSetAvailable", "status": "True", "type": "Progressing" } ], "observedGeneration": 1, "readyReplicas": 3, "replicas": 3, "updatedReplicas": 3 } } ] ================================================ FILE: test/resources/endpoints.json ================================================ [ { "apiVersion": "v1", "kind": "Endpoints", "metadata": { "creationTimestamp": "2021-04-08T14:59:12Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:subsets": {} }, "manager": "kubectl", "operation": "Update", "time": "2021-04-08T14:59:12Z" } ], "name": "my-service", "namespace": "default", "resourceVersion": "1278", "selfLink": "/api/v1/namespaces/default/endpoints/my-service", "uid": "05b4c49e-0d93-4c66-b46b-0454d23721b8" }, "subsets": [ { "addresses": [ { "ip": "192.0.2.42" } ], "ports": [ { "port": 9376, "protocol": "TCP" } ] } ] } ] ================================================ FILE: test/resources/hpa.json ================================================ [ { "apiVersion": "autoscaling/v1", "kind": "HorizontalPodAutoscaler", "metadata": { "annotations": { "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-05-31T20:25:56Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-05-31T20:25:56Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: missing request for cpu\"}]", "meta.helm.sh/release-name": "kubelib-test", "meta.helm.sh/release-namespace": "kubelib-tests" }, "creationTimestamp": "2021-05-31T20:25:39Z", "labels": { "app.kubernetes.io/managed-by": "Helm" }, "name": "kubelib-test-test-objects-chart", "namespace": "kubelib-tests", "resourceVersion": "280454922", "selfLink": "/apis/autoscaling/v1/namespaces/kubelib-tests/horizontalpodautoscalers/kubelib-test-test-objects-chart", "uid": "283300de-6de9-434d-bc8a-f7caeae65dcc" }, "spec": { "maxReplicas": 5, "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "kubelib-test-test-objects-chart" }, "targetCPUUtilizationPercentage": 50 }, "status": { "currentReplicas": 1, "desiredReplicas": 0 } } ] ================================================ FILE: test/resources/hpa_details.json ================================================ { "apiVersion": "autoscaling/v1", "kind": "HorizontalPodAutoscaler", "metadata": { "annotations": { "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-05-31T20:25:56Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-05-31T20:25:56Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: missing request for cpu\"}]", "meta.helm.sh/release-name": "kubelib-test", "meta.helm.sh/release-namespace": "kubelib-tests" }, "creationTimestamp": "2021-05-31T20:25:39Z", "labels": { "app.kubernetes.io/managed-by": "Helm" }, "name": "kubelib-test-test-objects-chart", "namespace": "kubelib-tests", "resourceVersion": "280454922", "selfLink": "/apis/autoscaling/v1/namespaces/kubelib-tests/horizontalpodautoscalers/kubelib-test-test-objects-chart", "uid": "283300de-6de9-434d-bc8a-f7caeae65dcc" }, "spec": { "maxReplicas": 5, "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "kubelib-test-test-objects-chart" }, "targetCPUUtilizationPercentage": 50 }, "status": { "currentReplicas": 1, "desiredReplicas": 0 } } ================================================ FILE: test/resources/ingress.json ================================================ [ { "apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": { "creationTimestamp": "2021-03-25T04:04:21Z", "generation": 1, "managedFields": [ { "apiVersion": "networking.k8s.io/v1beta1", "fieldsType": "FieldsV1", "fieldsV1": { "f:spec": { "f:rules": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-25T04:04:21Z" } ], "name": "minimal-ingress", "namespace": "default", "resourceVersion": "17529", "selfLink": "/apis/extensions/v1beta1/namespaces/default/ingresses/minimal-ingress", "uid": "e2dc27e7-4a75-4837-bf6a-9a13d7cbaaf3" }, "spec": { "rules": [ { "http": { "paths": [ { "backend": { "serviceName": "test", "servicePort": 80 }, "path": "/testpath", "pathType": "ImplementationSpecific" } ] } } ] }, "status": { "loadBalancer": {} } } ] ================================================ FILE: test/resources/ingress_details.json ================================================ { "apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": { "creationTimestamp": "2021-03-25T04:04:21Z", "labels": { "TestLabel": "mytestlabel" }, "generation": 1, "managedFields": [ { "apiVersion": "networking.k8s.io/v1beta1", "fieldsType": "FieldsV1", "fieldsV1": { "f:spec": { "f:rules": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-25T04:04:21Z" } ], "name": "minimal-ingress", "namespace": "default", "resourceVersion": "17529", "selfLink": "/apis/extensions/v1beta1/namespaces/default/ingresses/minimal-ingress", "uid": "e2dc27e7-4a75-4837-bf6a-9a13d7cbaaf3" }, "spec": { "rules": [ { "http": { "paths": [ { "backend": { "serviceName": "test", "servicePort": 80 }, "path": "/testpath", "pathType": "ImplementationSpecific" } ] } } ] }, "status": { "loadBalancer": {} } } ================================================ FILE: test/resources/jobs.json ================================================ [ { "apiVersion": "batch/v1", "kind": "Job", "metadata": { "creationTimestamp": "2020-12-04T14:53:42Z", "labels": { "controller-uid": "cce59116-9392-4eb0-b9bb-b3a72dd4d835", "job-name": "octopus-0" }, "name": "octopus-0", "namespace": "default", "ownerReferences": [ { "apiVersion": "batch/v1beta", "blockOwnerDeletion": true, "kind": "CronJob", "name": "monitoring-job-prod", "uid": "e296649c-3c4e-11ea-8cdc-42010a9a0056" } ], "resourceVersion": "259049170", "selfLink": "/apis/batch/v1/namespaces/default/jobs/octopus-0", "uid": "cce59116-9392-4eb0-b9bb-b3a72dd4d835" }, "spec": { "backoffLimit": 6, "completions": 1, "parallelism": 1, "selector": { "matchLabels": { "controller-uid": "cce59116-9392-4eb0-b9bb-b3a72dd4d835" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "controller-uid": "cce59116-9392-4eb0-b9bb-b3a72dd4d835", "job-name": "octopus-0" } }, "spec": { "containers": [ ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "completionTime": "2020-12-04T14:53:46Z", "conditions": [ { "lastProbeTime": "2020-12-04T14:53:46Z", "lastTransitionTime": "2020-12-04T14:53:46Z", "status": "True", "type": "Complete" } ], "startTime": "2020-12-04T14:53:42Z", "succeeded": 1 } }, { "apiVersion": "batch/v1", "kind": "Job", "metadata": { "creationTimestamp": "2020-11-30T11:14:09Z", "labels": { "controller-uid": "1c63b471-812b-4aae-8168-842d8e37b916", "job-name": "octopus-1" }, "name": "octopus-1", "namespace": "default", "ownerReferences": [ { "apiVersion": "batch/v1beta", "blockOwnerDeletion": true, "kind": "CronJob", "name": "monitoring-job-staging", "uid": "826080f1-3c50-11ea-8cdc-42010a9a0056" } ], "resourceVersion": "256386178", "selfLink": "/apis/batch/v1/namespaces/default/jobs/octopus-1", "uid": "1c63b471-812b-4aae-8168-842d8e37b916" }, "spec": { "backoffLimit": 6, "completions": 1, "parallelism": 1, "selector": { "matchLabels": { "controller-uid": "1c63b471-812b-4aae-8168-842d8e37b916" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "controller-uid": "1c63b471-812b-4aae-8168-842d8e37b916", "job-name": "octopus-1" } }, "spec": { "containers": [ ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "completionTime": "2020-11-30T11:14:12Z", "conditions": [ { "lastProbeTime": "2020-11-30T11:14:12Z", "lastTransitionTime": "2020-11-30T11:14:12Z", "status": "True", "type": "Complete" } ], "startTime": "2020-11-30T11:14:09Z", "succeeded": 1 } }, { "apiVersion": "batch/v1", "kind": "Job", "metadata": { "creationTimestamp": "2020-12-08T08:30:29Z", "labels": { "controller-uid": "58238c85-7d02-442c-8f87-45a7c26f233d", "job-name": "octopus-2" }, "name": "octopus-2", "namespace": "default", "ownerReferences": [ { "apiVersion": "batch/v1beta", "blockOwnerDeletion": true, "kind": "CronJob", "name": "monitoring-job-staging", "uid": "826080f1-3c50-11ea-8cdc-42010a9a0056" } ], "resourceVersion": "261430323", "selfLink": "/apis/batch/v1/namespaces/default/jobs/octopus-2", "uid": "58238c85-7d02-442c-8f87-45a7c26f233d" }, "spec": { "backoffLimit": 6, "completions": 1, "parallelism": 1, "selector": { "matchLabels": { "controller-uid": "58238c85-7d02-442c-8f87-45a7c26f233d" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "controller-uid": "58238c85-7d02-442c-8f87-45a7c26f233d", "job-name": "octopus-2" } }, "spec": { "containers": [ ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "completionTime": "2020-12-08T08:31:07Z", "conditions": [ { "lastProbeTime": "2020-12-08T08:31:07Z", "lastTransitionTime": "2020-12-08T08:31:07Z", "status": "True", "type": "Complete" } ], "startTime": "2020-12-08T08:30:29Z", "succeeded": 1 } }, { "apiVersion": "batch/v1", "kind": "Job", "metadata": { "creationTimestamp": "2020-12-10T08:43:36Z", "labels": { "controller-uid": "9d0ca880-a0f2-48e6-9b7d-af348c3d0ba1", "job-name": "octopus-3" }, "name": "octopus-3", "namespace": "default", "ownerReferences": [ { "apiVersion": "batch/v1beta", "blockOwnerDeletion": true, "kind": "CronJob", "name": "monitoring-job-staging", "uid": "826080f1-3c50-11ea-8cdc-42010a9a0056" } ], "resourceVersion": "262715903", "selfLink": "/apis/batch/v1/namespaces/default/jobs/octopus-3", "uid": "9d0ca880-a0f2-48e6-9b7d-af348c3d0ba1" }, "spec": { "backoffLimit": 6, "completions": 1, "parallelism": 1, "selector": { "matchLabels": { "controller-uid": "9d0ca880-a0f2-48e6-9b7d-af348c3d0ba1" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "controller-uid": "9d0ca880-a0f2-48e6-9b7d-af348c3d0ba1", "job-name": "octopus-3" } }, "spec": { "containers": [ ], "dnsPolicy": "ClusterFirst", "restartPolicy": "OnFailure", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "completionTime": "2020-12-10T08:43:58Z", "conditions": [ { "lastProbeTime": "2020-12-10T08:43:58Z", "lastTransitionTime": "2020-12-10T08:43:58Z", "status": "True", "type": "Complete" } ], "startTime": "2020-12-10T08:43:36Z", "succeeded": 1 } } ] ================================================ FILE: test/resources/k3d ================================================ apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T1RFeE5Ua3dNREFlRncweU1EQTVNRE13TmpVeE5EQmFGdzB6TURBNU1ERXdOalV4TkRCYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T1RFeE5Ua3dNREJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkJ0NzF6OER1UGZpZWIvNXI4ZVdaZ2pmL1Z0UnBUMFNNOWdEaG5zTTFseFAKSGdaUFgvU3k1NzFKWFJqbFhpTEdzYmZlaEV4RU5mWmNiUXlpQTZHVkxPYWpJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUdnUVUwc2k1a2FLCm83cDRlaFdTZFJLZ1JhajJTa3B2YlUwVVdZNElEQ0krQWlBTDhQbjhoUm1oWjB0QUFwY0dKZzROR0d3eE0wTG0KWCtHUEt5bk9qRmlDK1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== server: https://localhost:6443 name: k3s-default contexts: - context: cluster: k3s-default user: k3s-default name: k3s-default current-context: k3s-default kind: Config preferences: {} users: - name: k3s-default user: password: 68dd2cb21de7b909c7b12f3a57c0054f username: admin ================================================ FILE: test/resources/multiple_context ================================================ apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWekNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFl4TXpVNU1UUXhNVEFlRncweU1UQXlNVGN4T1RVd01URmFGdzB6TVRBeU1UVXhPVFV3TVRGYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFl4TXpVNU1UUXhNVEJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkl4T0ZvVnFtdlZ3ZzlHcE1ER0ZOOUprelAzQlVMQWdwSGErYWhta0E5TVUKK1hhMFFxdnZJRUFDNmluZDh1dzdxVTlPcXVXUGZ1M3FyUFU2QitTV3F4ZWpJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUd3Um81Y2crMFhOCjZ5cGQvVlhtc2tSQ1MrWjBHV2xSVU54L010S0dGc1dWQWlFQWs0UkNPOFRXVGx5eTVNcHQwcHRaNzBMVlEyWWMKZUdacjJUcFEvTU5uTFJBPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== server: https://0.0.0.0:38531 name: k3d-k3d-cluster - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWakNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFl4TXpVNU1UVXlOREFlRncweU1UQXlNVGN4T1RVeU1EUmFGdzB6TVRBeU1UVXhPVFV5TURSYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFl4TXpVNU1UVXlOREJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQko1bG0xelBrNXRyTUV0RHJaL2FNcEk5Rm1hYkJGTmlWbFhYeGc1N1BXWm8KWVNHNHFSbXJRV05YNzFtVG5qV20rSk1ITUhaa21NalJidzd0RmRvUVJOU2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURVWHUvRCtIdGJXClEzRTNlbEtGNHp6N3dMSjNmYXRYbGZpNmtUL0NCanlhQWlCcGo3NktMR1htZ1ZKQ0VrRVBSU3AvSjJ5NkkzMlUKN0JyOG02UG1aaHlJSWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== server: https://0.0.0.0:37971 name: k3d-k3d-cluster2 contexts: - context: cluster: k3d-k3d-cluster user: admin@k3d-k3d-cluster name: k3d-k3d-cluster - context: cluster: k3d-k3d-cluster2 user: admin@k3d-k3d-cluster2 name: k3d-k3d-cluster2 current-context: k3d-k3d-cluster kind: Config preferences: {} users: - name: admin@k3d-k3d-cluster user: password: 7723eb490bd1900a0686c9dcc55de40e username: admin - name: admin@k3d-k3d-cluster2 user: password: 34e01dc12a8d5054aceead504dec1112 username: admin ================================================ FILE: test/resources/namespaces.json ================================================ [ { "apiVersion": "v1", "kind": "Namespace", "metadata": { "creationTimestamp": "2021-01-11T13:11:58Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:status": { "f:phase": {} } }, "manager": "k3s", "operation": "Update", "time": "2021-01-11T13:11:58Z" } ], "name": "default", "resourceVersion": "5", "uid": "e068db23-0f18-4535-9821-42b7acf9ca72" }, "spec": { "finalizers": [ "kubernetes" ] }, "status": { "phase": "Active" } }, { "apiVersion": "v1", "kind": "Namespace", "metadata": { "creationTimestamp": "2021-01-11T13:11:58Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:status": { "f:phase": {} } }, "manager": "k3s", "operation": "Update", "time": "2021-01-11T13:11:58Z" } ], "name": "kubelib-test-test-objects-chart", "resourceVersion": "5", "uid": "e068db23-0f18-4535-9821-42b7acf9ca72" }, "spec": { "finalizers": [ "kubernetes" ] }, "status": { "phase": "Active" } } ] ================================================ FILE: test/resources/node_info.json ================================================ { "apiVersion": "v1", "items": [ { "apiVersion": "v1", "kind": "Node", "metadata": { "annotations": { "flannel.alpha.coreos.com/backend-data": "{\"VtepMAC\":\"62:e5:a6:42:d6:58\"}", "flannel.alpha.coreos.com/backend-type": "vxlan", "flannel.alpha.coreos.com/kube-subnet-manager": "true", "flannel.alpha.coreos.com/public-ip": "172.21.0.2", "k3s.io/node-args": "[\"server\",\"--tls-san\",\"0.0.0.0\"]", "k3s.io/node-config-hash": "D7CBSUVNY5FSJVW3U5ZIAK74FHUOS5TZOOMQE6FTIWUTHA7QHGKQ====", "k3s.io/node-env": "{\"K3S_KUBECONFIG_OUTPUT\":\"/output/kubeconfig.yaml\",\"K3S_TOKEN\":\"********\"}", "node.alpha.kubernetes.io/ttl": "0", "volumes.kubernetes.io/controller-managed-attach-detach": "true" }, "creationTimestamp": "2021-01-11T13:12:07Z", "finalizers": [ "wrangler.cattle.io/node" ], "labels": { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "k3s", "beta.kubernetes.io/os": "linux", "k3s.io/hostname": "k3d-k3s-default-server-0", "k3s.io/internal-ip": "172.21.0.2", "kubernetes.io/arch": "amd64", "kubernetes.io/hostname": "k3d-k3s-default-server-0", "kubernetes.io/os": "linux", "node-role.kubernetes.io/control-plane": "true", "node-role.kubernetes.io/master": "true", "node.kubernetes.io/instance-type": "k3s" }, "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:annotations": { ".": {}, "f:flannel.alpha.coreos.com/backend-data": {}, "f:flannel.alpha.coreos.com/backend-type": {}, "f:flannel.alpha.coreos.com/kube-subnet-manager": {}, "f:flannel.alpha.coreos.com/public-ip": {}, "f:k3s.io/node-args": {}, "f:k3s.io/node-config-hash": {}, "f:k3s.io/node-env": {}, "f:node.alpha.kubernetes.io/ttl": {}, "f:volumes.kubernetes.io/controller-managed-attach-detach": {} }, "f:finalizers": { ".": {}, "v:\"wrangler.cattle.io/node\"": {} }, "f:labels": { ".": {}, "f:beta.kubernetes.io/arch": {}, "f:beta.kubernetes.io/instance-type": {}, "f:beta.kubernetes.io/os": {}, "f:k3s.io/hostname": {}, "f:k3s.io/internal-ip": {}, "f:kubernetes.io/arch": {}, "f:kubernetes.io/hostname": {}, "f:kubernetes.io/os": {}, "f:node-role.kubernetes.io/control-plane": {}, "f:node-role.kubernetes.io/master": {}, "f:node.kubernetes.io/instance-type": {} } }, "f:spec": { "f:podCIDR": {}, "f:podCIDRs": { ".": {}, "v:\"10.42.0.0/24\"": {} }, "f:providerID": {} }, "f:status": { "f:addresses": { ".": {}, "k:{\"type\":\"Hostname\"}": { ".": {}, "f:address": {}, "f:type": {} }, "k:{\"type\":\"InternalIP\"}": { ".": {}, "f:address": {}, "f:type": {} } }, "f:allocatable": { ".": {}, "f:cpu": {}, "f:ephemeral-storage": {}, "f:hugepages-1Gi": {}, "f:hugepages-2Mi": {}, "f:memory": {}, "f:pods": {} }, "f:capacity": { ".": {}, "f:cpu": {}, "f:ephemeral-storage": {}, "f:hugepages-1Gi": {}, "f:hugepages-2Mi": {}, "f:memory": {}, "f:pods": {} }, "f:conditions": { ".": {}, "k:{\"type\":\"DiskPressure\"}": { ".": {}, "f:lastHeartbeatTime": {}, "f:lastTransitionTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} }, "k:{\"type\":\"MemoryPressure\"}": { ".": {}, "f:lastHeartbeatTime": {}, "f:lastTransitionTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} }, "k:{\"type\":\"NetworkUnavailable\"}": { ".": {}, "f:lastHeartbeatTime": {}, "f:lastTransitionTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} }, "k:{\"type\":\"PIDPressure\"}": { ".": {}, "f:lastHeartbeatTime": {}, "f:lastTransitionTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} }, "k:{\"type\":\"Ready\"}": { ".": {}, "f:lastHeartbeatTime": {}, "f:lastTransitionTime": {}, "f:message": {}, "f:reason": {}, "f:status": {}, "f:type": {} } }, "f:daemonEndpoints": { "f:kubeletEndpoint": { "f:Port": {} } }, "f:images": {}, "f:nodeInfo": { "f:architecture": {}, "f:bootID": {}, "f:containerRuntimeVersion": {}, "f:kernelVersion": {}, "f:kubeProxyVersion": {}, "f:kubeletVersion": {}, "f:operatingSystem": {}, "f:osImage": {}, "f:systemUUID": {} } } }, "manager": "k3s", "operation": "Update", "time": "2021-01-11T13:12:37Z" } ], "name": "k3d-k3s-default-server-0", "resourceVersion": "4047", "uid": "3251c14a-52ad-448a-bd1f-575ccd4f4654" }, "spec": { "podCIDR": "10.42.0.0/24", "podCIDRs": [ "10.42.0.0/24" ], "providerID": "k3s://k3d-k3s-default-server-0" }, "status": { "addresses": [ { "address": "172.21.0.2", "type": "InternalIP" }, { "address": "k3d-k3s-default-server-0", "type": "Hostname" } ], "allocatable": { "cpu": "2", "ephemeral-storage": "59589342571", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "memory": "6089228Ki", "pods": "110" }, "capacity": { "cpu": "2", "ephemeral-storage": "61255492Ki", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "memory": "6089228Ki", "pods": "110" }, "conditions": [ { "lastHeartbeatTime": "2021-01-11T13:12:15Z", "lastTransitionTime": "2021-01-11T13:12:15Z", "message": "Flannel is running on this node", "reason": "FlannelIsUp", "status": "False", "type": "NetworkUnavailable" }, { "lastHeartbeatTime": "2021-01-11T14:33:56Z", "lastTransitionTime": "2021-01-11T13:12:07Z", "message": "kubelet has sufficient memory available", "reason": "KubeletHasSufficientMemory", "status": "False", "type": "MemoryPressure" }, { "lastHeartbeatTime": "2021-01-11T14:33:56Z", "lastTransitionTime": "2021-01-11T13:12:07Z", "message": "kubelet has no disk pressure", "reason": "KubeletHasNoDiskPressure", "status": "False", "type": "DiskPressure" }, { "lastHeartbeatTime": "2021-01-11T14:33:56Z", "lastTransitionTime": "2021-01-11T13:12:07Z", "message": "kubelet has sufficient PID available", "reason": "KubeletHasSufficientPID", "status": "False", "type": "PIDPressure" }, { "lastHeartbeatTime": "2021-01-11T14:33:56Z", "lastTransitionTime": "2021-01-11T13:12:14Z", "message": "kubelet is posting ready status", "reason": "KubeletReady", "status": "True", "type": "Ready" } ], "daemonEndpoints": { "kubeletEndpoint": { "Port": 10250 } }, "images": [ { "names": [ "docker.io/rancher/klipper-helm@sha256:33f8d2b6961b76a10826d75194d8678a0e3ad4d715abf8e7370ac552a91eb0f0", "docker.io/rancher/klipper-helm:v0.3.2" ], "sizeBytes": 50733006 }, { "names": [ "docker.io/rancher/library-traefik@sha256:3ba3ed48c4632f2b02671923950b30b5b7f1b556e559ce15446d1f5d648a037d", "docker.io/rancher/library-traefik:1.7.19" ], "sizeBytes": 24011762 }, { "names": [ "docker.io/rancher/local-path-provisioner@sha256:40cb8c984c1759f1860eee088035040f47051c959a6d07cdb126e132c6f43b45", "docker.io/rancher/local-path-provisioner:v0.0.14" ], "sizeBytes": 13367922 }, { "names": [ "docker.io/rancher/coredns-coredns@sha256:8b675d12eb9faf3121475b12db478ac2cf5129046d92137aa9067dd04f3b4e10", "docker.io/rancher/coredns-coredns:1.8.0" ], "sizeBytes": 12944537 }, { "names": [ "docker.io/rancher/metrics-server@sha256:b85628b103169d7db52a32a48b46d8942accb7bde3709c0a4888a23d035f9f1e", "docker.io/rancher/metrics-server:v0.3.6" ], "sizeBytes": 10543877 }, { "names": [ "docker.io/rancher/klipper-lb@sha256:2fb97818f5d64096d635bc72501a6cb2c8b88d5d16bc031cf71b5b6460925e4a", "docker.io/rancher/klipper-lb:v0.1.2" ], "sizeBytes": 2708293 }, { "names": [ "docker.io/rancher/pause@sha256:d22591b61e9c2b52aecbf07106d5db313c4f178e404d660b32517b18fcbf0144", "docker.io/rancher/pause:3.1" ], "sizeBytes": 326597 } ], "node_info": { "architecture": "amd64", "bootID": "b1f9f0eb-1d1d-4fca-80e0-8bd41c453043", "containerRuntimeVersion": "containerd://1.4.3-k3s1", "kernelVersion": "5.4.39-linuxkit", "kubeProxyVersion": "v1.20.0+k3s2", "kubelet_version": "v1.20.0+k3s2", "machineID": "", "operatingSystem": "linux", "osImage": "Unknown", "systemUUID": "6a2946ae-0000-0000-a337-34a5b5720c9a" } } } ], "kind": "List", "metadata": { "resourceVersion": "", "selfLink": "" } } ================================================ FILE: test/resources/pod.json ================================================ { "api_version": "v1", "kind": "Pod", "metadata": { "name": "Mock" }, "spec": { "containers": [ { "name": "Mock", "image": "nginx:latest" } ] } } ================================================ FILE: test/resources/pod_status.json ================================================ { "api_version": "v1", "kind": "Pod", "metadata": { "annotations": { "checksum/config": "6554cd280cbfc996221d287c63c1bae2c5d3306173b19e6920ea3d6ad7ebe979", "checksum/dashboards-json-config": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "checksum/sc-dashboard-provider-config": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "checksum/secret": "82880dc31f393c9c5167c9c3981b345b14971aaca804f8333e82938059d9a59c", "kubelibrary": "testing" }, "cluster_name": null, "creation_timestamp": 1592598289, "deletion_grace_period_seconds": null, "deletion_timestamp": null, "finalizers": null, "generate_name": "grafana-6769d4b669-", "generation": null, "labels": { "app.kubernetes.io/instance": "grafana", "app.kubernetes.io/name": "grafana", "pod-template-hash": "6769d4b669" }, "managed_fields": null, "name": "grafana-6769d4b669-fhspj", "namespace": "default", "owner_references": [ { "api_version": "apps/v1", "block_owner_deletion": true, "controller": true, "kind": "ReplicaSet", "name": "grafana-6769d4b669", "uid": "fc2e7570-5d8e-422a-a52e-8396e78ec8ae" } ], "resource_version": "721", "self_link": "/api/v1/namespaces/default/pods/grafana-6769d4b669-fhspj/status", "uid": "304ed46c-a11d-49e0-adcb-c0bf1a333376" }, "spec": { "active_deadline_seconds": null, "affinity": null, "automount_service_account_token": true, "containers": [ { "args": null, "command": null, "env": [ { "name": "GF_SECURITY_ADMIN_USER", "value": null, "value_from": { "config_map_key_ref": null, "field_ref": null, "resource_field_ref": null, "secret_key_ref": { "key": "admin-user", "name": "grafana", "optional": null } } }, { "name": "GF_SECURITY_ADMIN_PASSWORD", "value": null, "value_from": { "config_map_key_ref": null, "field_ref": null, "resource_field_ref": null, "secret_key_ref": { "key": "admin-password", "name": "grafana", "optional": null } } }, { "name": "GF_PATHS_DATA", "value": "/var/lib/grafana/", "value_from": null }, { "name": "GF_PATHS_LOGS", "value": "/var/log/grafana", "value_from": null }, { "name": "GF_PATHS_PLUGINS", "value": "/var/lib/grafana/plugins", "value_from": null }, { "name": "GF_PATHS_PROVISIONING", "value": "/etc/grafana/provisioning", "value_from": null } ], "env_from": null, "image": "grafana/grafana:8.1.2", "image_pull_policy": "IfNotPresent", "lifecycle": null, "liveness_probe": { "_exec": null, "failure_threshold": 10, "http_get": { "host": null, "http_headers": null, "path": "/api/health", "port": 3000, "scheme": "HTTP" }, "initial_delay_seconds": 60, "period_seconds": 10, "success_threshold": 1, "tcp_socket": null, "timeout_seconds": 30 }, "name": "grafana", "ports": [ { "container_port": 3000, "host_ip": null, "host_port": null, "name": "service", "protocol": "TCP" }, { "container_port": 3000, "host_ip": null, "host_port": null, "name": "grafana", "protocol": "TCP" } ], "readiness_probe": { "_exec": null, "failure_threshold": 3, "http_get": { "host": null, "http_headers": null, "path": "/api/health", "port": 3000, "scheme": "HTTP" }, "initial_delay_seconds": null, "period_seconds": 10, "success_threshold": 1, "tcp_socket": null, "timeout_seconds": 1 }, "resources": { "limits": null, "requests": null }, "security_context": null, "startup_probe": null, "stdin": null, "stdin_once": null, "termination_message_path": "/dev/termination-log", "termination_message_policy": "File", "tty": null, "volume_devices": null, "volume_mounts": [ { "mount_path": "/etc/grafana/grafana.ini", "mount_propagation": null, "name": "config", "read_only": null, "sub_path": "grafana.ini", "sub_path_expr": null }, { "mount_path": "/var/lib/grafana", "mount_propagation": null, "name": "storage", "read_only": null, "sub_path": null, "sub_path_expr": null }, { "mount_path": "/var/run/secrets/kubernetes.io/serviceaccount", "mount_propagation": null, "name": "grafana-token-k79kp", "read_only": true, "sub_path": null, "sub_path_expr": null } ], "working_dir": null } ], "dns_config": null, "dns_policy": "ClusterFirst", "enable_service_links": true, "ephemeral_containers": null, "host_aliases": null, "host_ipc": null, "host_network": null, "host_pid": null, "hostname": null, "image_pull_secrets": null, "init_containers": [ { "args": null, "command": [ "chown", "-R", "472:472", "/var/lib/grafana" ], "env": null, "env_from": null, "image": "busybox:1.31.1", "image_pull_policy": "IfNotPresent", "lifecycle": null, "liveness_probe": null, "name": "init-chown-data", "ports": null, "readiness_probe": null, "resources": { "limits": null, "requests": null }, "security_context": { "allow_privilege_escalation": null, "capabilities": null, "privileged": null, "proc_mount": null, "read_only_root_filesystem": null, "run_as_group": null, "run_as_non_root": false, "run_as_user": 0, "se_linux_options": null, "windows_options": null }, "startup_probe": null, "stdin": null, "stdin_once": null, "termination_message_path": "/dev/termination-log", "termination_message_policy": "File", "tty": null, "volume_devices": null, "volume_mounts": [ { "mount_path": "/var/lib/grafana", "mount_propagation": null, "name": "storage", "read_only": null, "sub_path": null, "sub_path_expr": null }, { "mount_path": "/var/run/secrets/kubernetes.io/serviceaccount", "mount_propagation": null, "name": "grafana-token-k79kp", "read_only": true, "sub_path": null, "sub_path_expr": null } ], "working_dir": null } ], "node_name": "k3d-k3d-cluster-server-0", "node_selector": null, "overhead": null, "preemption_policy": null, "priority": 0, "priority_class_name": null, "readiness_gates": null, "restart_policy": "Always", "runtime_class_name": null, "scheduler_name": "default-scheduler", "security_context": { "fs_group": 472, "run_as_group": 472, "run_as_non_root": null, "run_as_user": 472, "se_linux_options": null, "supplemental_groups": null, "sysctls": null, "windows_options": null }, "service_account": "grafana", "service_account_name": "grafana", "share_process_namespace": null, "subdomain": null, "termination_grace_period_seconds": 30, "tolerations": [ { "effect": "NoExecute", "key": "node.kubernetes.io/not-ready", "operator": "Exists", "toleration_seconds": 300, "value": null }, { "effect": "NoExecute", "key": "node.kubernetes.io/unreachable", "operator": "Exists", "toleration_seconds": 300, "value": null } ], "topology_spread_constraints": null, "volumes": [ { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": { "default_mode": 420, "items": null, "name": "grafana", "optional": null }, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "config", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": null, "storageos": null, "vsphere_volume": null }, { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "storage", "nfs": null, "persistent_volume_claim": { "claim_name": "grafana", "read_only": null }, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": null, "storageos": null, "vsphere_volume": null }, { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "grafana-token-k79kp", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": { "default_mode": 420, "items": null, "optional": null, "secret_name": "grafana-token-k79kp" }, "storageos": null, "vsphere_volume": null } ] }, "status": { "conditions": [ { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Initialized" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Ready" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "ContainersReady" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "PodScheduled" } ], "container_statuses": [ { "container_id": "containerd://b317cd72f71779c0020d319d05cecfa67dafd19eeda4fc35c004f34507ea8a2d", "image": "docker.io/grafana/grafana:8.1.2", "image_id": "docker.io/grafana/grafana@sha256:811ee7d685fe45e5625928716d189c518f2b96edaa86122a04cc6faf1e988180", "last_state": { "running": null, "terminated": null, "waiting": null }, "name": "grafana", "ready": true, "restart_count": 0, "started": true, "state": { "running": { "started_at": 1592598289 }, "terminated": null, "waiting": null } } ], "ephemeral_container_statuses": null, "host_ip": "172.18.0.2", "init_container_statuses": [ { "container_id": "containerd://927ed23ec8d2d46c69f0decbbedeedc235b423f1ece2f8d7ad56512d04708eaa", "image": "docker.io/library/busybox:1.31.1", "image_id": "docker.io/library/busybox@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209", "last_state": { "running": null, "terminated": null, "waiting": null }, "name": "init-chown-data", "ready": true, "restart_count": 0, "started": null, "state": { "running": null, "terminated": { "container_id": "containerd://927ed23ec8d2d46c69f0decbbedeedc235b423f1ece2f8d7ad56512d04708eaa", "exit_code": 0, "finished_at": 1592598289, "message": null, "reason": "Completed", "signal": null, "started_at": 1592598289 }, "waiting": null } } ], "message": null, "nominated_node_name": null, "phase": "Running", "pod_i_ps": [ { "ip": "10.42.0.11" } ], "pod_ip": "10.42.0.11", "qos_class": "BestEffort", "reason": null, "start_time": 1592598289 } } ================================================ FILE: test/resources/pods.json ================================================ [ { "api_version": null, "kind": null, "metadata": { "annotations": null, "cluster_name": null, "creation_timestamp": 1592598289, "deletion_grace_period_seconds": null, "deletion_timestamp": null, "finalizers": null, "generate_name": "octopus-", "generation": null, "initializers": null, "labels": { "app": "octopus", "control-plane": "controller-manager", "controller-revision-hash": "octopus-7678656bfb", "controller-tools.k8s.io": "1.0", "statefulset.kubernetes.io/pod-name": "octopus-0" }, "managed_fields": [ { "api_version": "v1", "fields": null, "manager": "k3s", "operation": "Update", "time": 1592598289 } ], "name": "octopus-0", "namespace": "default", "owner_references": [ { "api_version": "apps/v1", "block_owner_deletion": true, "controller": true, "kind": "StatefulSet", "name": "octopus", "uid": "294fabba-95ba-451d-b3f5-d495e834c839" } ], "resource_version": "739", "self_link": "/api/v1/namespaces/default/pods/octopus-0", "uid": "61b189e8-5c7e-46f1-b4af-8476a68da7a6" }, "spec": { "active_deadline_seconds": null, "affinity": null, "automount_service_account_token": null, "containers": [ { "args": null, "command": [ "/manager" ], "env": [ { "name": "POD_NAMESPACE", "value": null, "value_from": { "config_map_key_ref": null, "field_ref": { "api_version": "v1", "field_path": "metadata.namespace" }, "resource_field_ref": null, "secret_key_ref": null } }, { "name": "SECRET_NAME", "value": "webhook-server-secret", "value_from": null } ], "env_from": null, "image": "eu.gcr.io/kyma-project/incubator/develop/octopus:dc5dc284", "image_pull_policy": "Always", "lifecycle": null, "liveness_probe": null, "name": "manager", "ports": [ { "container_port": 9876, "host_ip": null, "host_port": null, "name": "webhook-server", "protocol": "TCP" } ], "readiness_probe": null, "resources": { "limits": { "cpu": "100m", "memory": "30Mi" }, "requests": { "cpu": "100m", "memory": "20Mi" } }, "security_context": null, "stdin": null, "stdin_once": null, "termination_message_path": "/dev/termination-log", "termination_message_policy": "File", "tty": null, "volume_devices": null, "volume_mounts": [ { "mount_path": "/tmp/cert", "mount_propagation": null, "name": "cert", "read_only": true, "sub_path": null, "sub_path_expr": null }, { "mount_path": "/var/run/secrets/kubernetes.io/serviceaccount", "mount_propagation": null, "name": "octopus-token-d6m9q", "read_only": true, "sub_path": null, "sub_path_expr": null } ], "working_dir": null } ], "dns_config": null, "dns_policy": "ClusterFirst", "enable_service_links": true, "host_aliases": null, "host_ipc": null, "host_network": null, "host_pid": null, "hostname": "octopus-0", "image_pull_secrets": null, "init_containers": null, "node_name": "k3d-k3s-default-worker-0", "node_selector": null, "preemption_policy": null, "priority": 0, "priority_class_name": null, "readiness_gates": null, "restart_policy": "Always", "runtime_class_name": null, "scheduler_name": "default-scheduler", "security_context": { "fs_group": null, "run_as_group": null, "run_as_non_root": null, "run_as_user": null, "se_linux_options": null, "supplemental_groups": null, "sysctls": null, "windows_options": null }, "service_account": "octopus", "service_account_name": "octopus", "share_process_namespace": null, "subdomain": "octopus", "termination_grace_period_seconds": 10, "tolerations": [ { "effect": "NoExecute", "key": "node.kubernetes.io/not-ready", "operator": "Exists", "toleration_seconds": 300, "value": null }, { "effect": "NoExecute", "key": "node.kubernetes.io/unreachable", "operator": "Exists", "toleration_seconds": 300, "value": null } ], "volumes": [ { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "cert", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": { "default_mode": 420, "items": null, "optional": null, "secret_name": "webhook-server-secret" }, "storageos": null, "vsphere_volume": null }, { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "octopus-token-d6m9q", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": { "default_mode": 420, "items": null, "optional": null, "secret_name": "octopus-token-d6m9q" }, "storageos": null, "vsphere_volume": null } ] }, "status": { "conditions": [ { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Initialized" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Ready" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "ContainersReady" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "PodScheduled" } ], "container_statuses": [ { "container_id": "containerd://0e85dc1bab1bf21c1de37b7a8026d1ce62231018a0ac28e72b0f2e95aa09d062", "image": "eu.gcr.io/kyma-project/incubator/develop/octopus:dc5dc284", "image_id": "eu.gcr.io/kyma-project/incubator/develop/octopus@sha256:bf9ebb15cb0aa127478efd0ec37bb223ab1f5cba3a112cfd3db61a6b0ccea733", "last_state": { "running": null, "terminated": null, "waiting": null }, "name": "manager", "ready": true, "restart_count": 0, "state": { "running": { "started_at": 1592598289 }, "terminated": null, "waiting": null } } ], "host_ip": "172.18.0.3", "init_container_statuses": null, "message": null, "nominated_node_name": null, "phase": "Running", "pod_ip": "10.42.1.6", "qos_class": "Burstable", "reason": null, "start_time": 1592598289 } }, { "api_version": null, "kind": null, "metadata": { "annotations": { "checksum/config": "1c42968a1b9eca0bafc3273ca39c4705fe71dc632e721db9e8ce44ab1b8e1428", "checksum/dashboards-json-config": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "checksum/sc-dashboard-provider-config": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "checksum/secret": "11c4f1a28559266593f5ea89cbeabcdf26a3d14d003bd356a82a5250d3261803" }, "cluster_name": null, "creation_timestamp": 1592598289, "deletion_grace_period_seconds": null, "deletion_timestamp": null, "finalizers": null, "generate_name": "grafana-5d9895c6c4-", "generation": null, "initializers": null, "labels": { "app.kubernetes.io/instance": "grafana", "app.kubernetes.io/name": "grafana", "pod-template-hash": "5d9895c6c4" }, "managed_fields": [ { "api_version": "v1", "fields": null, "manager": "k3s", "operation": "Update", "time": 1592598289 } ], "name": "grafana-5d9895c6c4-sfsn8", "namespace": "default", "owner_references": [ { "api_version": "apps/v1", "block_owner_deletion": true, "controller": true, "kind": "ReplicaSet", "name": "grafana-5d9895c6c4", "uid": "c2e555c9-be8d-4bcc-940e-39c8a5d7ed56" } ], "resource_version": "924", "self_link": "/api/v1/namespaces/default/pods/grafana-5d9895c6c4-sfsn8", "uid": "c0d1dc6b-2de8-4edc-be28-b0cffd1eaa1b" }, "spec": { "active_deadline_seconds": null, "affinity": null, "automount_service_account_token": null, "containers": [ { "args": null, "command": null, "env": [ { "name": "GF_SECURITY_ADMIN_USER", "value": null, "value_from": { "config_map_key_ref": null, "field_ref": null, "resource_field_ref": null, "secret_key_ref": { "key": "admin-user", "name": "grafana", "optional": null } } }, { "name": "GF_SECURITY_ADMIN_PASSWORD", "value": null, "value_from": { "config_map_key_ref": null, "field_ref": null, "resource_field_ref": null, "secret_key_ref": { "key": "admin-password", "name": "grafana", "optional": null } } } ], "env_from": null, "image": "grafana/grafana:7.0.3", "image_pull_policy": "IfNotPresent", "lifecycle": null, "liveness_probe": { "_exec": null, "failure_threshold": 10, "http_get": { "host": null, "http_headers": null, "path": "/api/health", "port": 3000, "scheme": "HTTP" }, "initial_delay_seconds": 60, "period_seconds": 10, "success_threshold": 1, "tcp_socket": null, "timeout_seconds": 30 }, "name": "grafana", "ports": [ { "container_port": 80, "host_ip": null, "host_port": null, "name": "service", "protocol": "TCP" }, { "container_port": 3000, "host_ip": null, "host_port": null, "name": "grafana", "protocol": "TCP" } ], "readiness_probe": { "_exec": null, "failure_threshold": 3, "http_get": { "host": null, "http_headers": null, "path": "/api/health", "port": 3000, "scheme": "HTTP" }, "initial_delay_seconds": null, "period_seconds": 10, "success_threshold": 1, "tcp_socket": null, "timeout_seconds": 1 }, "resources": { "limits": null, "requests": null }, "security_context": null, "stdin": null, "stdin_once": null, "termination_message_path": "/dev/termination-log", "termination_message_policy": "File", "tty": null, "volume_devices": null, "volume_mounts": [ { "mount_path": "/etc/grafana/grafana.ini", "mount_propagation": null, "name": "config", "read_only": null, "sub_path": "grafana.ini", "sub_path_expr": null }, { "mount_path": "/var/lib/grafana", "mount_propagation": null, "name": "storage", "read_only": null, "sub_path": null, "sub_path_expr": null }, { "mount_path": "/var/run/secrets/kubernetes.io/serviceaccount", "mount_propagation": null, "name": "grafana-token-ghbbk", "read_only": true, "sub_path": null, "sub_path_expr": null } ], "working_dir": null } ], "dns_config": null, "dns_policy": "ClusterFirst", "enable_service_links": true, "host_aliases": null, "host_ipc": null, "host_network": null, "host_pid": null, "hostname": null, "image_pull_secrets": null, "init_containers": null, "node_name": "k3d-k3s-default-worker-0", "node_selector": null, "preemption_policy": null, "priority": 0, "priority_class_name": null, "readiness_gates": null, "restart_policy": "Always", "runtime_class_name": null, "scheduler_name": "default-scheduler", "security_context": { "fs_group": 472, "run_as_group": 472, "run_as_non_root": null, "run_as_user": 472, "se_linux_options": null, "supplemental_groups": null, "sysctls": null, "windows_options": null }, "service_account": "grafana", "service_account_name": "grafana", "share_process_namespace": null, "subdomain": null, "termination_grace_period_seconds": 30, "tolerations": [ { "effect": "NoExecute", "key": "node.kubernetes.io/not-ready", "operator": "Exists", "toleration_seconds": 300, "value": null }, { "effect": "NoExecute", "key": "node.kubernetes.io/unreachable", "operator": "Exists", "toleration_seconds": 300, "value": null } ], "volumes": [ { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": { "default_mode": 420, "items": null, "name": "grafana", "optional": null }, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "config", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": null, "storageos": null, "vsphere_volume": null }, { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": { "medium": null, "size_limit": null }, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "storage", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": null, "storageos": null, "vsphere_volume": null }, { "aws_elastic_block_store": null, "azure_disk": null, "azure_file": null, "cephfs": null, "cinder": null, "config_map": null, "csi": null, "downward_api": null, "empty_dir": null, "fc": null, "flex_volume": null, "flocker": null, "gce_persistent_disk": null, "git_repo": null, "glusterfs": null, "host_path": null, "iscsi": null, "name": "grafana-token-ghbbk", "nfs": null, "persistent_volume_claim": null, "photon_persistent_disk": null, "portworx_volume": null, "projected": null, "quobyte": null, "rbd": null, "scale_io": null, "secret": { "default_mode": 420, "items": null, "optional": null, "secret_name": "grafana-token-ghbbk" }, "storageos": null, "vsphere_volume": null } ] }, "status": { "conditions": [ { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Initialized" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "Ready" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "ContainersReady" }, { "last_probe_time": null, "last_transition_time": 1592598289, "message": null, "reason": null, "status": "true", "type": "PodScheduled" } ], "container_statuses": [ { "container_id": "containerd://17dbd103229c2e2b738ccfa2a373bd7ab3d79c7ad6e682ed785c3e9fc510ee41", "image": "docker.io/grafana/grafana:7.0.3", "image_id": "docker.io/grafana/grafana@sha256:d72946c8e5d57a9a121bcc3ae8e4a8ccab96960d81031d18a4c31ad1f7aea03e", "last_state": { "running": null, "terminated": null, "waiting": null }, "name": "grafana", "ready": true, "restart_count": 0, "state": { "running": { "started_at": 1592598289 }, "terminated": null, "waiting": null } } ], "host_ip": "172.18.0.3", "init_container_statuses": null, "message": null, "nominated_node_name": null, "phase": "Running", "pod_ip": "10.42.1.7", "qos_class": "BestEffort", "reason": null, "start_time": 1592598289 } } ] ================================================ FILE: test/resources/pvc.json ================================================ [ { "apiVersion": "v1", "kind": "PersistentVolumeClaim", "metadata": { "creationTimestamp": "2021-03-19T03:42:46Z", "finalizers": [ "kubernetes.io/pvc-protection" ], "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:spec": { "f:accessModes": {}, "f:resources": { "f:requests": { ".": {}, "f:storage": {} } }, "f:selector": { ".": {}, "f:matchExpressions": {}, "f:matchLabels": { ".": {}, "f:release": {} } }, "f:storageClassName": {}, "f:volumeMode": {} }, "f:status": { "f:phase": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-19T03:42:46Z" } ], "name": "myclaim", "namespace": "default", "resourceVersion": "4686", "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim", "uid": "42708d93-02f5-4488-9bae-497d1a150de8" }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "8Gi" } }, "selector": { "matchExpressions": [ { "key": "environment", "operator": "In", "values": [ "dev" ] } ], "matchLabels": { "release": "stable" } }, "storageClassName": "slow", "volumeMode": "Filesystem" }, "status": { "phase": "Pending" } } ] ================================================ FILE: test/resources/replicaset.json ================================================ [ { "apiVersion": "apps/v1", "kind": "ReplicaSet", "metadata": { "creationTimestamp": "2021-07-22T00:56:58Z", "generation": 1, "labels": { "app": "nginx-proxy", "tier": "frontend" }, "name": "nginx-proxy", "namespace": "test-auto", "resourceVersion": "94849081", "selfLink": "/apis/apps/v1/namespaces/test-auto/replicasets/nginx-proxy", "uid": "3c64a9fb-b891-475b-905b-84ec84e079ee" }, "spec": { "replicas": 5, "selector": { "matchLabels": { "tier": "frontend" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "tier": "frontend" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "Always", "name": "nginx", "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "fullyLabeledReplicas": 5, "observedGeneration": 1, "replicas": 5 } } ] ================================================ FILE: test/resources/role.json ================================================ [ { "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "metadata": { "creationTimestamp": "2021-03-10T04:54:29Z", "managedFields": [ { "apiVersion": "rbac.authorization.k8s.io/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:rules": {} }, "manager": "kubectl", "operation": "Update", "time": "2021-03-10T04:54:29Z" } ], "name": "pod-reader", "namespace": "default", "resourceVersion": "2157", "selfLink": "/apis/rbac.authorization.k8s.io/v1/namespaces/default/roles/pod-reader", "uid": "f2f5ebcf-d1fa-4607-9d9e-7872ef185614" }, "rules": [ { "apiGroups": [ "" ], "resources": [ "pods" ], "verbs": [ "get", "watch", "list" ] } ] } ] ================================================ FILE: test/resources/rolebinding.json ================================================ [ { "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "RoleBinding", "metadata": { "creationTimestamp": "2021-03-10T04:59:42Z", "managedFields": [ { "apiVersion": "rbac.authorization.k8s.io/v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:roleRef": { "f:apiGroup": {}, "f:kind": {}, "f:name": {} }, "f:subjects": {} }, "manager": "kubectl", "operation": "Update", "time": "2021-03-10T04:59:42Z" } ], "name": "read-pods", "namespace": "default", "resourceVersion": "2952", "selfLink": "/apis/rbac.authorization.k8s.io/v1/namespaces/default/rolebindings/read-pods", "uid": "0df37c75-ea20-4c8a-979a-1a2a67faf1db" }, "roleRef": { "apiGroup": "rbac.authorization.k8s.io", "kind": "Role", "name": "pod-reader" }, "subjects": [ { "apiGroup": "rbac.authorization.k8s.io", "kind": "User", "name": "jane" } ] } ] ================================================ FILE: test/resources/secrets.json ================================================ [ { "apiVersion": "v1", "data": { "admin-password": "NERwMHFKcjFtOWtPR0p3UW1GZFFYbXVVRU4xZ3dYemM0T09neXlGWg==", "admin-user": "YWRtaW4=", "ldap-toml": "" }, "kind": "Secret", "metadata": { "creationTimestamp": "2021-02-06T10:57:31Z", "labels": { "app.kubernetes.io/instance": "grafana", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "grafana", "app.kubernetes.io/version": "7.1.1", "helm.sh/chart": "grafana-5.5.7" }, "name": "grafana", "namespace": "default", "resourceVersion": "207273733", "selfLink": "/api/v1/namespaces/default/secrets/grafana", "uid": "46ddd8a0-8ae9-47bb-8d4d-232ab1584594" }, "type": "Opaque" } ] ================================================ FILE: test/resources/service.json ================================================ [ { "apiVersion": "v1", "kind": "Service", "metadata": { "creationTimestamp": "2021-03-17T12:27:33Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:spec": { "f:ports": { ".": {}, "k:{\"port\":80,\"protocol\":\"TCP\"}": { ".": {}, "f:port": {}, "f:protocol": {}, "f:targetPort": {} } }, "f:selector": { ".": {}, "f:app": {} }, "f:sessionAffinity": {}, "f:type": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-03-17T12:27:33Z" } ], "name": "test-service", "namespace": "default", "resourceVersion": "970", "selfLink": "/api/v1/namespaces/default/services/my-service", "uid": "bd0c1526-e540-4e80-aae6-088731013f1a" }, "spec": { "clusterIP": "10.108.127.133", "ports": [ { "port": 80, "protocol": "TCP", "targetPort": 9376 } ], "selector": { "app": "MyApp" }, "sessionAffinity": "None", "type": "ClusterIP" }, "status": { "loadBalancer": {} } } ] ================================================ FILE: test/resources/service_accounts.json ================================================ [ { "apiVersion": "v1", "kind": "ServiceAccount", "metadata": { "creationTimestamp": "2021-01-11T13:12:14Z", "name": "default", "namespace": "default", "resourceVersion": "383", "uid": "5a9e50d5-6e31-47a0-b747-cf3d60316d19" }, "secrets": [ { "name": "default-token-wtcl2" } ] }, { "apiVersion": "v1", "kind": "ServiceAccount", "metadata": { "creationTimestamp": "2021-01-11T13:12:14Z", "name": "kubelib-test-test-objects-chart", "namespace": "default", "resourceVersion": "383", "uid": "5a9e50d5-6e31-47a0-b747-cf3d60316d19" }, "secrets": [ { "name": "default-token-wtcl2" } ] } ] ================================================ FILE: test/resources/service_details.json ================================================ { "apiVersion": "v1", "kind": "Service", "metadata": { "creationTimestamp": "2021-04-22T10:24:37Z", "labels": { "Test": "mytest" }, "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:labels": { ".": {}, "f:Test": {} } }, "f:spec": { "f:ports": { ".": {}, "k:{\"port\":80,\"protocol\":\"TCP\"}": { ".": {}, "f:port": {}, "f:protocol": {}, "f:targetPort": {} } }, "f:selector": { ".": {}, "f:app": {} }, "f:sessionAffinity": {}, "f:type": {} } }, "manager": "kubectl", "operation": "Update", "time": "2021-04-22T10:24:37Z" } ], "name": "my-service2", "namespace": "default", "resourceVersion": "909", "selfLink": "/api/v1/namespaces/default/services/my-service2", "uid": "6fd4d12c-8b5f-4a0b-b153-96489ab3c0be" }, "spec": { "clusterIP": "10.97.158.206", "ports": [ { "port": 80, "protocol": "TCP", "targetPort": 9376 } ], "selector": { "app": "MyApp" }, "sessionAffinity": "None", "type": "ClusterIP" }, "status": { "loadBalancer": {} } } ================================================ FILE: test/resources/sts.json ================================================ [ { "apiVersion": "apps/v1", "kind": "StatefulSet", "metadata": { "creationTimestamp": "2021-07-21T00:56:58Z", "generation": 1, "labels": { "app": "nginx-proxy", "tier": "frontend" }, "name": "nginx-proxy", "namespace": "default", "resourceVersion": "94859081", "selfLink": "/apis/apps/v1/namespaces/default/statefulsets/nginx-proxy", "uid": "3c64a9fb-b891-475b-905b-84ec84e079e1" }, "spec": { "replicas": 5, "selector": { "matchLabels": { "tier": "frontend" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "tier": "frontend" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "Always", "name": "nginx", "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "fullyLabeledReplicas": 5, "observedGeneration": 1, "replicas": 5 } } ] ================================================ FILE: test/test_KubeLibrary.py ================================================ import json import mock import re import ssl import unittest from KubeLibrary import KubeLibrary from KubeLibrary.exceptions import BearerTokenWithPrefixException from kubernetes.config.config_exception import ConfigException from urllib3_mock import Responses class AttributeDict(object): """ Based on http://databio.org/posts/python_AttributeDict.html A class to convert a nested Dictionary into an object with key-values accessibly using attribute notation (AttributeDict.attribute) instead of key notation (Dict["key"]). This class recursively sets Dicts to objects, allowing you to recurse down nested dicts (like: AttributeDict.attr.attr) """ def __init__(self, entries): self._root = None self.add_entries(entries) def add_entries(self, entries): self._root = entries for key, value in entries.items(): if type(value) is dict: self.__dict__[key] = AttributeDict(value) elif type(value) is list: self.__dict__[key] = [AttributeDict(item) if type(item) is dict else item for item in value] else: self.__dict__[key] = value def __iter__(self): return iter(self._root) def __getitem__(self, key): """ Provides dict-style access to attributes """ return getattr(self, key) def mock_read_daemonset_details_in_namespace(name, namespace): if namespace == 'default': with open('test/resources/daemonset_details.json') as json_file: daemonset_details_content = json.load(json_file) read_daemonset_details = AttributeDict({'items': daemonset_details_content}) return read_daemonset_details def mock_read_service_details_in_namespace(name, namespace): if namespace == 'default': with open('test/resources/service_details.json') as json_file: service_details_content = json.load(json_file) read_service_details = AttributeDict({'items': service_details_content}) return read_service_details def mock_read_hpa_details_in_namespace(name, namespace): if namespace == 'default': with open('test/resources/hpa_details.json') as json_file: hpa_details_content = json.load(json_file) read_hpa_details = AttributeDict({'items': hpa_details_content}) return read_hpa_details def mock_read_ingress_details_in_namespace(name, namespace): if namespace == 'default': with open('test/resources/ingress_details.json') as json_file: ingress_details_content = json.load(json_file) read_ingress_details = AttributeDict({'items': ingress_details_content}) return read_ingress_details def mock_read_cron_job_details_in_namespace(name, namespace): if namespace == 'default': with open('test/resources/cronjob_details.json') as json_file: cron_job_details_content = json.load(json_file) read_cron_job_details = AttributeDict({'items': cron_job_details_content}) return read_cron_job_details def mock_list_namespaced_daemonsets(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/daemonset.json') as json_file: daemonsets_content = json.load(json_file) list_of_daemonsets = AttributeDict({'items': daemonsets_content}) return list_of_daemonsets def mock_list_namespaced_cronjobs(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/cronjob.json') as json_file: cronjobs_content = json.load(json_file) list_of_cronjobs = AttributeDict({'items': cronjobs_content}) return list_of_cronjobs def mock_list_namespaced_ingresses(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/ingress.json') as json_file: ingresses_content = json.load(json_file) list_ingresses = AttributeDict({'items': ingresses_content}) return list_ingresses def mock_read_namespaced_endpoints(name, namespace): if namespace == 'default': with open('test/resources/endpoints.json') as json_file: endpoints_content = json.load(json_file) read_endpoints = AttributeDict({'items': endpoints_content}) return read_endpoints def mock_list_namespaced_config_map(namespace, watch=False, label_selector=""): with open('test/resources/configmap.json') as json_file: configmap_content = json.load(json_file) configmap = AttributeDict({'items': configmap_content}) return configmap def mock_list_namespaced_deployments(namespace, watch=False, label_selector=""): with open('test/resources/deployment.json') as json_file: deployments_content = json.load(json_file) deployments = AttributeDict({'items': deployments_content}) return deployments def mock_list_namespaced_replicasets(namespace, watch=False, label_selector=""): with open('test/resources/replicaset.json') as json_file: replicasets_content = json.load(json_file) replicasets = AttributeDict({'items': replicasets_content}) return replicasets def mock_list_namespaced_statefulsets(namespace, watch=False, label_selector=""): with open('test/resources/sts.json') as json_file: statefulsets_content = json.load(json_file) statefulsets = AttributeDict({'items': statefulsets_content}) return statefulsets def mock_list_pvc(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/pvc.json') as json_file: pvc_content = json.load(json_file) list_pvc = AttributeDict({'items': pvc_content}) return list_pvc def mock_list_cluster_roles(watch=False): with open('test/resources/cluster_role.json') as json_file: cluster_roles_content = json.load(json_file) list_of_cluster_roles = AttributeDict({'items': cluster_roles_content}) return list_of_cluster_roles def mock_list_namespaced_services(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/service.json') as json_file: services_content = json.load(json_file) list_services = AttributeDict({'items': services_content}) return list_services def mock_list_namespaced_hpas(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/hpa.json') as json_file: hpas_content = json.load(json_file) list_hpas = AttributeDict({'items': hpas_content}) return list_hpas def mock_list_namespaced_pod(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/pods.json') as json_file: pods_content = json.load(json_file) list_of_pods = AttributeDict({'items': pods_content}) return list_of_pods def mock_read_namespaced_pod_status(name, namespace): if namespace == 'default': with open('test/resources/pod_status.json') as json_file: pod_content = json.load(json_file) pod_status = AttributeDict({'status': pod_content['status']}) return pod_status def mock_list_cluster_role_bindings(watch=False): with open('test/resources/cluster_role_bind.json') as json_file: cluster_role_bindings_content = json.load(json_file) list_of_cluster_role_bindings = AttributeDict({'items': cluster_role_bindings_content}) return list_of_cluster_role_bindings def mock_list_namespaced_service_accounts(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/service_accounts.json') as json_file: service_accounts_content = json.load(json_file) list_of_service_accounts = AttributeDict({'items': service_accounts_content}) return list_of_service_accounts def mock_list_namespaced_jobs(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/jobs.json') as json_file: jobs_content = json.load(json_file) list_of_jobs = AttributeDict({'items': jobs_content}) return list_of_jobs def mock_list_namespaced_secrets(namespace, watch=False, label_selector=""): if namespace == 'default': with open('test/resources/secrets.json') as json_file: secrets_content = json.load(json_file) list_of_secrets = AttributeDict({'items': secrets_content}) return list_of_secrets def mock_list_namespaces(watch=False, label_selector=""): with open('test/resources/namespaces.json') as json_file: namespaces_content = json.load(json_file) list_of_namespaces = AttributeDict({'items': namespaces_content}) return list_of_namespaces def mock_list_node_info(watch=False, label_selector=""): with open('test/resources/node_info.json') as json_file: node_info_content = json.load(json_file) node_info = AttributeDict(node_info_content) return node_info def mock_list_namespaced_roles(namespace, watch=False): if namespace == 'default': with open('test/resources/role.json') as json_file: role_content = json.load(json_file) list_of_role = AttributeDict({'items': role_content}) return list_of_role def mock_list_namespaced_role_bindings(namespace, watch=False): if namespace == 'default': with open('test/resources/rolebinding.json') as json_file: role_bind_content = json.load(json_file) list_of_role_bind = AttributeDict({'items': role_bind_content}) return list_of_role_bind def mock_k8s_version(): k8s_version = { 'major': '1', 'minor': '33', 'emulationMajor': '1', 'emulationMinor': '33', 'minCompatibilityMajor': '1', 'minCompatibilityMinor': '32', 'gitVersion': 'v1.33.5-gke.1308000', 'gitCommit': 'a53c2ee3f2e9859ad8413e216edd44aed40e011d', 'gitTreeState': 'clean', 'buildDate': '2025-10-13T04:22:26Z', 'goVersion': 'go1.24.6 X:boringcrypto', 'compiler': 'gc', 'platform': 'linux/amd64' } return k8s_version bearer_token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjdXVWJMOUdTaDB1TjcyNmF0Sjk4RWlzQ05RaWdSUFoyN004TmlGT1pSX28ifQ.' \ 'eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1' \ 'lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15c2EtdG' \ '9rZW4taDRzNzUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoibXlzY' \ 'SIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjY5MTk5ZmUyLTIzNWIt' \ 'NGY3MC04MjEwLTkzZTk2YmM5ZmEwOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Om15c2EifQ.' \ 'V8VIYZ0B2y2h9p-2LTZ19klSuZ37HUWi-8F1yjfFTq83R1Dmax6DoDr5gWbVL4A054q5k1L2U12d50gox0V_kVsRTb3' \ 'KQnRSGCz1YgCqOVNLqnnsyu3kcmDaUDrFlJ4PuZ7R4DfvGCdK-BU9pj2MhcQT-tyfbGR-dwwkjwXTCPRZVW-CUm4qwY' \ 'bCGTpGbNXPXbEKtseXIxMkRg70Kav3M-YB1LYHQRx_T2IqKAmyhXlbMc8boqoEiSi6TRbMjZ9Yz-nkc82e6kAdc1O2F' \ '4kFw-14kg2mX7Hu-02vob_LZmfR08UGu6VTkcfVK5VqZVg2oVBI4swZghQl8_fOtlplOg' ca_cert = '/path/to/certificate.crt' k8s_api_url = 'https://0.0.0.0:38041' responses = Responses('requests.packages.urllib3') class TestKubeLibrary(unittest.TestCase): apis = ('v1', 'networkingv1api', 'batchv1', 'appsv1', 'custom_object', 'rbac_authv1_api', 'autoscalingv1', 'dynamic') @responses.activate def test_KubeLibrary_inits_from_kubeconfig(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") KubeLibrary(kube_config='test/resources/k3d') @responses.activate def test_KubeLibrary_inits_with_context(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") KubeLibrary(kube_config='test/resources/multiple_context', context='k3d-k3d-cluster2') @responses.activate def test_KubeLibrary_fails_for_wrong_context(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(kube_config='test/resources/multiple_context') self.assertRaises(ConfigException, kl.reload_config, kube_config='test/resources/multiple_context', context='k3d-k3d-cluster2-wrong') @responses.activate def test_inits_all_api_clients(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(kube_config='test/resources/k3d') for api in TestKubeLibrary.apis: self.assertIsNotNone(getattr(kl, api)) @responses.activate def test_KubeLibrary_inits_without_cert_validation(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(kube_config='test/resources/k3d', cert_validation=False) for api in TestKubeLibrary.apis: target = getattr(kl, api) self.assertEqual(target.api_client.rest_client.pool_manager.connection_pool_kw['cert_reqs'], ssl.CERT_NONE) @responses.activate def test_KubeLibrary_inits_with_bearer_token(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(api_url=k8s_api_url, bearer_token=bearer_token) for api in TestKubeLibrary.apis: target = getattr(kl, api) self.assertEqual(kl.api_client.configuration.api_key, target.api_client.configuration.api_key) # self.assertEqual(kl.api_client.configuration.ssl_ca_cert, None) #seems this is now set by kubernetes client @responses.activate def test_inits_with_bearer_token_raises_BearerTokenWithPrefixException(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(api_url=k8s_api_url, bearer_token=bearer_token) self.assertRaises(BearerTokenWithPrefixException, kl.reload_config, api_url=k8s_api_url, bearer_token='Bearer prefix should fail') @responses.activate def test_KubeLibrary_inits_with_bearer_token_with_ca_crt(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") kl = KubeLibrary(api_url=k8s_api_url, bearer_token=bearer_token, ca_cert=ca_cert) self.assertEqual(kl.api_client.configuration.ssl_ca_cert, ca_cert) self.assertEqual(kl.dynamic.configuration.ssl_ca_cert, ca_cert) self.assertEqual(kl.dynamic.client.configuration.ssl_ca_cert, ca_cert) @responses.activate def test_KubeLibrary_dynamic_init(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") kl = KubeLibrary(kube_config='test/resources/k3d') resource = kl.get_dynamic_resource("v1", "Pod") self.assertTrue(hasattr(resource, "get")) self.assertTrue(hasattr(resource, "watch")) self.assertTrue(hasattr(resource, "delete")) self.assertTrue(hasattr(resource, "create")) self.assertTrue(hasattr(resource, "patch")) self.assertTrue(hasattr(resource, "replace")) @responses.activate def test_KubeLibrary_dynamic_get(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") responses.add("GET", "/api/v1/mock/Mock", status=200, body='{"api_version": "v1", "kind": "Pod", "name": "Mock", "msg": "My Mock Pod"}', content_type="application/json") kl = KubeLibrary(kube_config='test/resources/k3d') pod = kl.get("v1", "Pod", name="Mock") self.assertEqual(pod.msg, "My Mock Pod") @responses.activate def test_KubeLibrary_dynamic_patch(self): def mock_callback(request): self.assertEqual(request.body, '{"msg": "Mock"}') return (200, None, None) responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") responses.add_callback("PATCH", "/api/v1/mock/Mock", callback=mock_callback) kl = KubeLibrary(kube_config='test/resources/k3d') kl.patch("v1", "Pod", name="Mock", body={"msg": "Mock"}) @responses.activate def test_KubeLibrary_dynamic_replace(self): def mock_callback(request): self.assertEqual(request.body, '{"msg": "Mock"}') return (200, None, None) responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") responses.add_callback("PUT", "/api/v1/mock/Mock", callback=mock_callback) kl = KubeLibrary(kube_config='test/resources/k3d') kl.replace("v1", "Pod", name="Mock", body={"msg": "Mock"}) @responses.activate def test_KubeLibrary_dynamic_create(self): with open('test/resources/pod.json') as json_file: sample_pod = json.load(json_file) responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") responses.add("POST", "/api/v1/mock", status=200, body=json.dumps(sample_pod), content_type="application/json") kl = KubeLibrary(kube_config='test/resources/k3d') created = kl.create("v1", "Pod", body=json.dumps(sample_pod)) self.assertEqual(created.to_dict(), sample_pod) @responses.activate def test_KubeLibrary_dynamic_delete(self): responses.add("GET", "/version", status=200) responses.add("GET", "/apis", status=200, body='{"groups": [], "kind": "Pod" }', content_type="application/json") responses.add("GET", "/api/v1", status=200, body='{"resources": [{"api_version": "v1", "kind": "Pod", "name": "Mock"}], "kind": "Pod"}', content_type="application/json") responses.add("DELETE", "/api/v1/mock/Mock", status=200) kl = KubeLibrary(kube_config='test/resources/k3d') kl.delete("v1", "Pod", name="Mock") def test_generate_alphanumeric_str(self): name = KubeLibrary.generate_alphanumeric_str(10) self.assertEqual(10, len(name)) def test_evaluate_callable_from_k8s_client(self): configmap = KubeLibrary.evaluate_callable_from_k8s_client( attr_name="V1ConfigMap", data={"msg": "Mock"}, api_version="v1", kind="ConfigMap", metadata=KubeLibrary.evaluate_callable_from_k8s_client(attr_name="V1ObjectMeta", name="Mock") ) self.assertIsNotNone(configmap) self.assertEqual(configmap.metadata.name, "Mock") @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_list_namespaced_pod_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('.*', 'default') pods2 = kl.get_pods_in_namespace('.*', 'default') pods3 = kl.get_pod_names_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(pods), pods3) self.assertEqual(kl.filter_names(pods), kl.filter_pods_names(pods2)) self.assertEqual(['octopus-0', 'grafana-5d9895c6c4-sfsn8'], kl.filter_names(pods)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_get_matching_pods_in_namespace(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('graf.*', 'default') self.assertEqual(['grafana-5d9895c6c4-sfsn8'], kl.filter_names(pods)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_filter_pods_containers_by_name(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('octopus.*', 'default') self.assertEqual('manager', kl.filter_pods_containers_by_name(pods, '.*')[0].name) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_filter_containers_images(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('octopus.*', 'default') containers = kl.filter_pods_containers_by_name(pods, '.*') self.assertEqual(['eu.gcr.io/kyma-project/incubator/develop/octopus:dc5dc284'], kl.filter_containers_images(containers)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_filter_pods_containers_statuses_by_name(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('octopus.*', 'default') self.assertEqual(0, kl.filter_pods_containers_statuses_by_name(pods, '.*')[0].restart_count) @mock.patch('kubernetes.client.CoreV1Api.read_namespaced_pod_status') def test_read_namespaced_pod_status(self, mock_lnp): mock_lnp.side_effect = mock_read_namespaced_pod_status kl = KubeLibrary(kube_config='test/resources/k3d') pod_status = kl.read_namespaced_pod_status('grafana-6769d4b669-fhspj', 'default') self.assertEqual('Running', pod_status['phase']) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_pod') def test_filter_containers_resources(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_pod kl = KubeLibrary(kube_config='test/resources/k3d') pods = kl.list_namespaced_pod_by_pattern('octopus.*', 'default') containers = kl.filter_pods_containers_by_name(pods, '.*') self.assertEqual('100m', kl.filter_containers_resources(containers)[0].limits.cpu) def test_assert_pod_has_labels(self): pod = mock_list_namespaced_pod('default').items[0] kl = KubeLibrary(kube_config='test/resources/k3d') labels = '{}' self.assertTrue(kl.assert_pod_has_labels(pod, labels)) labels = '{"app":"octopus"}' self.assertTrue(kl.assert_pod_has_labels(pod, labels)) labels = '{"app":"wrong"}' self.assertFalse(kl.assert_pod_has_labels(pod, labels)) labels = '{"notexists":"octopus"}' self.assertFalse(kl.assert_pod_has_labels(pod, labels)) labels = '{"badlyformatted:}' self.assertTrue(kl.assert_pod_has_labels(pod, labels) is False) def test_assert_pod_has_annotations(self): pod = mock_list_namespaced_pod('default').items[1] kl = KubeLibrary(kube_config='test/resources/k3d') labels = '{}' self.assertTrue(kl.assert_pod_has_annotations(pod, labels)) labels = '{"checksum/config":"1c42968a1b9eca0bafc3273ca39c4705fe71dc632e721db9e8ce44ab1b8e1428"}' self.assertTrue(kl.assert_pod_has_annotations(pod, labels)) labels = '{"checksum/config":"wrong"}' self.assertFalse(kl.assert_pod_has_annotations(pod, labels)) labels = '{"notexists":"1c42968a1b9eca0bafc3273ca39c4705fe71dc632e721db9e8ce44ab1b8e1428"}' self.assertFalse(kl.assert_pod_has_annotations(pod, labels)) labels = '{"badlyformatted:}' self.assertTrue(kl.assert_pod_has_annotations(pod, labels) is False) def test_assert_container_has_env_vars(self): pod = mock_list_namespaced_pod('default').items[0] kl = KubeLibrary(kube_config='test/resources/k3d') container = kl.filter_pods_containers_by_name([pod], '.*')[0] env_vars = '{}' self.assertTrue(kl.assert_container_has_env_vars(container, env_vars)) env_vars = '{"SECRET_NAME":"webhook-server-secret"}' self.assertTrue(kl.assert_container_has_env_vars(container, env_vars)) env_vars = '{"SECRET_NAME":"wrong"}' self.assertFalse(kl.assert_container_has_env_vars(container, env_vars)) env_vars = '{"NOT_EXISTING":"wrong"}' self.assertFalse(kl.assert_container_has_env_vars(container, env_vars)) env_vars = '{"badlyformatted:}' self.assertFalse(kl.assert_container_has_env_vars(container, env_vars)) @unittest.skip("Will overwrite *.json") def test_gather_pods_obejcts_to_json(self): kl = KubeLibrary(kube_config='~/.kube/k3d') ret = kl.v1.read_namespaced_pod_status('grafana-6769d4b669-fhspj', 'default') pods_str = str(ret).replace("'", '"') \ .replace('None', 'null') \ .replace('True', 'true') \ .replace('False', 'false') # serialize datetime into fixed timestamp pods = re.sub(r'datetime(.+?)\)\)', '1592598289', pods_str) print(pods) with open('test/resources/pod_status.json', 'w') as outfile: json.dump(json.loads(pods), outfile, indent=4) @mock.patch('kubernetes.client.CoreV1Api.list_namespace') def test_list_namespace(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaces kl = KubeLibrary(kube_config='test/resources/k3d') namespaces = kl.list_namespace() namespaces2 = kl.get_namespaces() self.assertEqual(kl.filter_names(namespaces), namespaces2) self.assertTrue(len(namespaces) > 0) self.assertEqual(['default', 'kubelib-test-test-objects-chart'], kl.filter_names(namespaces)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_service_account') def test_list_namespaced_service_account_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_service_accounts kl = KubeLibrary(kube_config='test/resources/k3d') sa = kl.list_namespaced_service_account_by_pattern('.*', 'default') sa2 = kl.get_service_accounts_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(sa), kl.filter_service_accounts_names(sa2)) self.assertEqual(['default', 'kubelib-test-test-objects-chart'], kl.filter_names(sa)) @mock.patch('kubernetes.client.CoreV1Api.list_node') def test_get_kubelet_version(self, mock_lnp): mock_lnp.side_effect = mock_list_node_info kl = KubeLibrary(kube_config='test/resources/k3d') kl_version = kl.get_kubelet_version() self.assertTrue(len(kl_version) > 0) self.assertEqual(['v1.20.0+k3s2'], kl_version) @mock.patch('kubernetes.client.BatchV1Api.list_namespaced_job') def test_list_namespaced_job_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_jobs kl = KubeLibrary(kube_config='test/resources/k3d') jobs = kl.list_namespaced_job_by_pattern('.*', 'default') jobs2 = kl.get_jobs_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(jobs), kl.filter_names(jobs2)) self.assertEqual(['octopus-0', 'octopus-1', 'octopus-2', 'octopus-3'], kl.filter_names(jobs)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_secret') def test_list_namespaced_secret_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_secrets kl = KubeLibrary(kube_config='test/resources/k3d') secrets = kl.list_namespaced_secret_by_pattern('.*', 'default') secrets2 = kl.get_secrets_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(secrets), kl.filter_names(secrets2)) self.assertEqual(['grafana'], kl.filter_names(secrets)) @mock.patch('kubernetes.stream.stream') def test_get_namespaced_exec_without_container(self, mock_stream): test_string = "This is test String!" mock_stream.return_value = test_string kl = KubeLibrary(kube_config='test/resources/k3d') stdout = kl.get_namespaced_pod_exec(name="pod_name", namespace="default", argv_cmd=["/bin/bash", "-c", f"echo {test_string}"]) self.assertFalse("container" in mock_stream.call_args.kwargs.keys()) self.assertEqual(stdout, test_string) @mock.patch('kubernetes.stream.stream') def test_get_namespaced_exec_with_container(self, mock_stream): test_string = "This is test String!" mock_stream.return_value = test_string kl = KubeLibrary(kube_config='test/resources/k3d') stdout = kl.get_namespaced_pod_exec(name="pod_name", namespace="default", container="manager", argv_cmd=["/bin/bash", "-c", f"echo {test_string}"]) self.assertTrue("container" in mock_stream.call_args.kwargs.keys()) self.assertTrue("manager" in mock_stream.call_args.kwargs.values()) self.assertEqual(stdout, test_string) @mock.patch('kubernetes.stream.stream') def test_get_namespaced_exec_not_argv_and_list(self, mock_stream): test_string = "This is test String!" ex = f"argv_cmd parameter should be a list and contains values like " \ f"[\"/bin/bash\", \"-c\", \"ls\"] not echo {test_string}" mock_stream.return_value = test_string kl = KubeLibrary(kube_config='test/resources/k3d') with self.assertRaises(TypeError) as cm: kl.get_namespaced_pod_exec(name="pod_name", namespace="default", container="manager", argv_cmd=f"echo {test_string}") self.assertEqual(str(cm.exception), ex) @mock.patch('kubernetes.client.RbacAuthorizationV1Api.list_cluster_role') def test_list_cluster_role(self, mock_lnp): mock_lnp.side_effect = mock_list_cluster_roles kl = KubeLibrary(kube_config='test/resources/k3d') cluster_roles = kl.list_cluster_role() cluster_roles2 = kl.get_cluster_roles() self.assertEqual(kl.filter_names(cluster_roles), cluster_roles2) self.assertEqual(['secret-reader'], kl.filter_names(cluster_roles)) @mock.patch('kubernetes.client.RbacAuthorizationV1Api.list_cluster_role_binding') def test_list_cluster_role_binding(self, mock_lnp): mock_lnp.side_effect = mock_list_cluster_role_bindings kl = KubeLibrary(kube_config='test/resources/k3d') cluster_role_bindings = kl.list_cluster_role_binding() cluster_role_bindings2 = kl.get_cluster_role_bindings() self.assertEqual(kl.filter_names(cluster_role_bindings), cluster_role_bindings2) self.assertEqual(['read-secrets-global'], kl.filter_names(cluster_role_bindings)) @mock.patch('kubernetes.client.RbacAuthorizationV1Api.list_namespaced_role') def test_list_namespaced_role(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_roles kl = KubeLibrary(kube_config='test/resources/k3d') roles = kl.list_namespaced_role('default') roles2 = kl.get_roles_in_namespace('default') self.assertEqual(kl.filter_names(roles), roles2) self.assertEqual(['pod-reader'], kl.filter_names(roles)) @mock.patch('kubernetes.client.RbacAuthorizationV1Api.list_namespaced_role_binding') def test_list_namespaced_role_binding(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_role_bindings kl = KubeLibrary(kube_config='test/resources/k3d') role_bindings = kl.list_namespaced_role_binding('default') role_bindings2 = kl.get_role_bindings_in_namespace('default') self.assertEqual(kl.filter_names(role_bindings), role_bindings2) self.assertEqual(['read-pods'], kl.filter_names(role_bindings)) @mock.patch('kubernetes.client.AppsV1Api.list_namespaced_deployment') def test_list_namespaced_deployment_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_deployments kl = KubeLibrary(kube_config='test/resources/k3d') deployments = kl.list_namespaced_deployment_by_pattern('.*', 'default') deployments2 = kl.get_deployments_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(deployments), kl.filter_deployments_names(deployments2)) self.assertEqual(['nginx-deployment'], kl.filter_names(deployments)) @mock.patch('kubernetes.client.AppsV1Api.list_namespaced_replica_set') def test_list_namespaced_replica_set_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_replicasets kl = KubeLibrary(kube_config='test/resources/k3d') replicasets = kl.list_namespaced_replica_set_by_pattern('.*', 'test-auto') replicasets2 = kl.get_replicasets_in_namespace('.*', 'test-auto') self.assertEqual(kl.filter_names(replicasets), kl.filter_replicasets_names(replicasets2)) self.assertEqual(['nginx-proxy'], kl.filter_names(replicasets)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_persistent_volume_claim') def test_list_namespaced_persistent_volume_claim(self, mock_lnp): mock_lnp.side_effect = mock_list_pvc kl = KubeLibrary(kube_config='test/resources/k3d') pvcs = kl.list_namespaced_persistent_volume_claim('default') pvcs2 = kl.get_pvc_in_namespace('default') self.assertEqual(kl.filter_names(pvcs), pvcs2) self.assertEqual(['myclaim'], kl.filter_names(pvcs)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_persistent_volume_claim') def test_list_namespaced_persistent_volume_claim_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_pvc kl = KubeLibrary(kube_config='test/resources/k3d') pvcs = kl.list_namespaced_persistent_volume_claim_by_pattern('.*', 'default') pvcs2 = kl.get_pvc_in_namespace('default') self.assertEqual(kl.filter_names(pvcs), pvcs2) self.assertEqual(['myclaim'], kl.filter_names(pvcs)) @mock.patch('kubernetes.client.AppsV1Api.list_namespaced_stateful_set') def test_list_namespaced_stateful_set_by_pattern(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_statefulsets kl = KubeLibrary(kube_config='test/resources/k3d') statefulsets = kl.list_namespaced_stateful_set_by_pattern('.*', 'default') statefulsets2 = kl.list_namespaced_stateful_set('default') self.assertEqual(kl.filter_names(statefulsets), kl.filter_names(statefulsets2)) self.assertEqual(['nginx-proxy'], kl.filter_names(statefulsets)) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_service') def test_list_namespaced_service(self, mock_service): mock_service.side_effect = mock_list_namespaced_services kl = KubeLibrary(kube_config='test/resources/k3d') ret = kl.list_namespaced_service('default') self.assertEqual('test-service', ret[0].metadata.name) @mock.patch('kubernetes.client.AppsV1Api.list_namespaced_daemon_set') def test_list_namespaced_daemon_set(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_daemonsets kl = KubeLibrary(kube_config='test/resources/k3d') daemonsets = kl.list_namespaced_daemon_set('default') daemonsets2 = kl.get_daemonsets_in_namespace('default') self.assertEqual(kl.filter_names(daemonsets), daemonsets2) self.assertEqual(['fluentd-elasticsearch'], kl.filter_names(daemonsets)) @mock.patch('kubernetes.client.NetworkingV1Api.list_namespaced_ingress') def test_list_namespaced_ingress(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_ingresses kl = KubeLibrary(kube_config='test/resources/k3d') ingresses = kl.list_namespaced_ingress('default') ingresses2 = kl.get_ingresses_in_namespace('default') self.assertEqual(kl.filter_names(ingresses), ingresses2) self.assertEqual(['minimal-ingress'], kl.filter_names(ingresses)) @mock.patch('kubernetes.client.BatchV1Api.list_namespaced_cron_job') def test_list_namespaced_cron_job(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_cronjobs kl = KubeLibrary(kube_config='test/resources/k3d') cronjobs = kl.list_namespaced_cron_job('default') cronjobs2 = kl.get_cron_jobs_in_namespace('default') self.assertEqual(kl.filter_names(cronjobs), cronjobs2) self.assertEqual(['hello'], kl.filter_names(cronjobs)) @mock.patch('kubernetes.client.CoreV1Api.read_namespaced_endpoints') def test_read_namespaced_endpoints(self, mock_lnp): mock_lnp.side_effect = mock_read_namespaced_endpoints kl = KubeLibrary(kube_config='test/resources/k3d') endpoints = kl.read_namespaced_endpoints('.*', 'default') endpoints2 = kl.get_endpoints_in_namespace('.*', 'default') self.assertEqual(endpoints.items[0].metadata.name, endpoints2.items[0].metadata.name) self.assertEqual('my-service', endpoints.items[0].metadata.name) @mock.patch('kubernetes.client.CoreV1Api.list_namespaced_config_map') def test_get_configmaps_in_namespace(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_config_map kl = KubeLibrary(kube_config='test/resources/k3d') configmaps = kl.list_namespaced_config_map_by_pattern('.*', 'default') configmaps2 = kl.get_configmaps_in_namespace('.*', 'default') self.assertEqual(kl.filter_names(configmaps), kl.filter_configmap_names(configmaps2)) self.assertEqual(['game-demo'], kl.filter_names(configmaps)) @mock.patch('kubernetes.client.AutoscalingV1Api.list_namespaced_horizontal_pod_autoscaler') def test_list_namespaced_horizontal_pod_autoscaler(self, mock_lnp): mock_lnp.side_effect = mock_list_namespaced_hpas kl = KubeLibrary(kube_config='test/resources/k3d') hpas = kl.list_namespaced_horizontal_pod_autoscaler('default') hpas2 = kl.get_hpas_in_namespace('default') self.assertEqual(kl.filter_names(hpas), hpas2) self.assertEqual(['kubelib-test-test-objects-chart'], kl.filter_names(hpas)) @mock.patch('kubernetes.client.AutoscalingV1Api.read_namespaced_horizontal_pod_autoscaler') def test_read_namespaced_horizontal_pod_autoscaler(self, mock_lnp): mock_lnp.side_effect = mock_read_hpa_details_in_namespace kl = KubeLibrary(kube_config='test/resources/k3d') hpa_details = kl.read_namespaced_horizontal_pod_autoscaler('kubelib-test-test-objects-chart', 'default') hpa_details2 = kl.get_hpa_details_in_namespace('kubelib-test-test-objects-chart', 'default') self.assertEqual(hpa_details.items.spec.scaleTargetRef.name, hpa_details2.items.spec.scaleTargetRef.name) self.assertEqual('kubelib-test-test-objects-chart', hpa_details.items.spec.scaleTargetRef.name) @mock.patch('kubernetes.client.AppsV1Api.read_namespaced_daemon_set') def test_read_namespaced_daemon_set(self, mock_lnp): mock_lnp.side_effect = mock_read_daemonset_details_in_namespace kl = KubeLibrary(kube_config='test/resources/k3d') daemonset_details = kl.read_namespaced_daemon_set('fluentd-elasticsearch', 'default') daemonset_details2 = kl.get_daemonset_details_in_namespace('fluentd-elasticsearch', 'default') self.assertEqual(daemonset_details.items.metadata.labels.TestLabel, daemonset_details2.items.metadata.labels.TestLabel) self.assertEqual('mytestlabel', daemonset_details.items.metadata.labels.TestLabel) @mock.patch('kubernetes.client.CoreV1Api.read_namespaced_service') def test_read_namespaced_service(self, mock_lnp): mock_lnp.side_effect = mock_read_service_details_in_namespace kl = KubeLibrary(kube_config='test/resources/k3d') service_details = kl.read_namespaced_service('minimal-ingress', 'default') service_details2 = kl.get_service_details_in_namespace('minimal-ingress', 'default') self.assertEqual(service_details.items.metadata.labels.Test, service_details2.items.metadata.labels.Test) self.assertEqual('mytest', service_details.items.metadata.labels.Test) @mock.patch('kubernetes.client.NetworkingV1Api.read_namespaced_ingress') def test_read_namespaced_ingress(self, mock_lnp): mock_lnp.side_effect = mock_read_ingress_details_in_namespace kl = KubeLibrary(kube_config='test/resources/k3d') ingress_details = kl.read_namespaced_ingress('max-ingress', 'default') ingress_details2 = kl.get_ingress_details_in_namespace('max-ingress', 'default') self.assertEqual(ingress_details.items.metadata.labels.TestLabel, ingress_details2.items.metadata.labels.TestLabel) self.assertEqual('mytestlabel', ingress_details.items.metadata.labels.TestLabel) @mock.patch('kubernetes.client.BatchV1Api.read_namespaced_cron_job') def test_read_namespaced_cron_job(self, mock_lnp): mock_lnp.side_effect = mock_read_cron_job_details_in_namespace kl = KubeLibrary(kube_config='test/resources/k3d') cron_job_details = kl.read_namespaced_cron_job('hello', 'default') cron_job_details2 = kl.get_cron_job_details_in_namespace('hello', 'default') self.assertEqual(cron_job_details.items.metadata.labels.TestLabel, cron_job_details2.items.metadata.labels.TestLabel) self.assertEqual('mytestlabel', cron_job_details.items.metadata.labels.TestLabel) def test_k8s_version(self): mock_resp = mock_k8s_version() self.assertEqual("v1.33.5-gke.1308000", str(mock_resp['gitVersion'])) ================================================ FILE: test-objects-chart/.helmignore ================================================ # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *~ # Various IDEs .project .idea/ *.tmproj .vscode/ ================================================ FILE: test-objects-chart/Chart.yaml ================================================ apiVersion: v2 name: test-objects-chart description: A Helm chart for Kubernetes # A chart can be either an 'application' or a 'library' chart. # # Application charts are a collection of templates that can be packaged into versioned archives # to be deployed. # # Library charts provide useful utilities or functions for the chart developer. They're included as # a dependency of application charts to inject those utilities and functions into the rendering # pipeline. Library charts do not define any templates and therefore cannot be deployed. type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. version: 0.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. appVersion: 1.16.0 ================================================ FILE: test-objects-chart/README.md ================================================ # Helm chart to install requirements for the tests With this helm chart kubernetes objects can be installed to a cluster as prerequisites to some of the tests for this library. ## Install with Connect to your k8s cluster, then ``` kubectl create namespace kubelib-tests helm install kubelib-test ./test-objects-chart -n kubelib-tests ``` ## Extending this helm chart Feel free to extend this helm chart which any other kubernetes objects which are required by any tests of this library. ================================================ FILE: test-objects-chart/templates/NOTES.txt ================================================ 1. Get the application URL by running these commands: {{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "test-objects-chart.fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "test-objects-chart.fullname" . }}' export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "test-objects-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "test-objects-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 {{- end }} ================================================ FILE: test-objects-chart/templates/_helpers.tpl ================================================ {{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "test-objects-chart.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "test-objects-chart.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "test-objects-chart.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Common labels */}} {{- define "test-objects-chart.labels" -}} helm.sh/chart: {{ include "test-objects-chart.chart" . }} {{ include "test-objects-chart.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} {{/* Selector labels */}} {{- define "test-objects-chart.selectorLabels" -}} app.kubernetes.io/name: {{ include "test-objects-chart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{/* Create the name of the service account to use */}} {{- define "test-objects-chart.serviceAccountName" -}} {{- if .Values.serviceAccount.create -}} {{ default (include "test-objects-chart.fullname" .) .Values.serviceAccount.name }} {{- else -}} {{ default "default" .Values.serviceAccount.name }} {{- end -}} {{- end -}} ================================================ FILE: test-objects-chart/templates/cluster_role.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: # "namespace" omitted since ClusterRoles are not namespaced name: secret-reader rules: - apiGroups: [""] # # at the HTTP level, the name of the resource for accessing Secret # objects is "secrets" resources: ["secrets"] verbs: ["get", "watch", "list"] ================================================ FILE: test-objects-chart/templates/cluster_role_bind.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 # This cluster role binding allows anyone in the "manager" group to read secrets in any namespace. kind: ClusterRoleBinding metadata: name: read-secrets-global subjects: - kind: Group name: manager # Name is case sensitive apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.io ================================================ FILE: test-objects-chart/templates/cronjob.yaml ================================================ apiVersion: batch/v1 kind: CronJob metadata: name: hello labels: TestLabel: mytestlabel spec: schedule: "*/1 * * * *" jobTemplate: spec: template: metadata: labels: TestLabel: mytestlabel spec: containers: - name: hello image: busybox imagePullPolicy: IfNotPresent args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure ================================================ FILE: test-objects-chart/templates/daemonset.yaml ================================================ apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-elasticsearch labels: TestLabel: mytestlabel spec: selector: matchLabels: name: fluentd-elasticsearch template: metadata: labels: name: fluentd-elasticsearch spec: tolerations: # this toleration is to have the daemonset runnable on master nodes # remove it if your masters can't run pods - 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 ================================================ FILE: test-objects-chart/templates/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "test-objects-chart.fullname" . }} labels: {{- include "test-objects-chart.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "test-objects-chart.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "test-objects-chart.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "test-objects-chart.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} ================================================ FILE: test-objects-chart/templates/hpa.yaml ================================================ apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "test-objects-chart.fullname" . }} spec: maxReplicas: 5 minReplicas: 1 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {{ include "test-objects-chart.fullname" . }} targetCPUUtilizationPercentage: 50 ================================================ FILE: test-objects-chart/templates/ingress.yaml ================================================ {{- if .Values.ingress.enabled -}} {{- $fullName := include "test-objects-chart.fullname" . -}} {{- $svcPort := .Values.service.port -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "test-objects-chart.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ . }} pathType: Prefix backend: service: name: {{ $fullName }} port: number: {{ $svcPort }} {{- end }} {{- end }} {{- end }} ================================================ FILE: test-objects-chart/templates/job.yml ================================================ apiVersion: batch/v1 kind: Job metadata: labels: TestLabel: mytestlabel name: busybox-job spec: backoffLimit: 1 completions: 1 parallelism: 1 template: metadata: spec: containers: - env: - name: MYENVVAR value: my-env-value image: busybox:latest command: ["ash"] args: ["-c", "export RANDOM_NAME=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1); while true; do sleep 5; echo I am $RANDOM_NAME at $(date); done"] imagePullPolicy: IfNotPresent name: busybox resources: {} dnsPolicy: ClusterFirst imagePullSecrets: - name: regcred restartPolicy: Never schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 ================================================ FILE: test-objects-chart/templates/role.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader rules: - apiGroups: [""] # "" indicates the core API group resources: ["pods"] verbs: ["get", "watch", "list"] ================================================ FILE: test-objects-chart/templates/rolebinding.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 # This role binding allows "jane" to read pods in the "default" namespace. # You need to already have a Role named "pod-reader" in that namespace. kind: RoleBinding metadata: name: read-pods subjects: # You can specify more than one "subject" - kind: User name: jane # "name" is case sensitive apiGroup: rbac.authorization.k8s.io roleRef: # "roleRef" specifies the binding to a Role / ClusterRole kind: Role #this must be Role or ClusterRole name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to apiGroup: rbac.authorization.k8s.io ================================================ FILE: test-objects-chart/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: {{ include "test-objects-chart.fullname" . }} labels: {{- include "test-objects-chart.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "test-objects-chart.selectorLabels" . | nindent 4 }} ================================================ FILE: test-objects-chart/templates/serviceaccount.yaml ================================================ {{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "test-objects-chart.serviceAccountName" . }} labels: {{ include "test-objects-chart.labels" . | nindent 4 }} {{- end -}} ================================================ FILE: test-objects-chart/templates/tests/test-connection.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: "{{ include "test-objects-chart.fullname" . }}-test-connection" labels: {{ include "test-objects-chart.labels" . | nindent 4 }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "test-objects-chart.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never ================================================ FILE: test-objects-chart/values.yaml ================================================ # Default values for test-objects-chart. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: nginx pullPolicy: IfNotPresent imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceAccount: # Specifies whether a service account should be created create: true # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: podSecurityContext: {} # fsGroup: 2000 securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 service: type: ClusterIP port: 80 ingress: enabled: true annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - host: chart-example.local paths: ["/"] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi nodeSelector: {} tolerations: [] affinity: {} ================================================ FILE: testcases/Dockerfile ================================================ FROM python:3.9.0-alpine COPY setup.py README.md requirements.txt ./ COPY src ./src COPY testcases ./testcases RUN pip install robotframework-requests \ && python setup.py install ENTRYPOINT ["robot"] CMD ["--help"] ================================================ FILE: testcases/cluster_role/cluster_role.robot ================================================ *** Settings *** Resource ./cluster_role_kw.robot *** Test Cases *** Cluster_role test case example [Tags] other List all cluster_roles List all cluster_role_bindings ================================================ FILE: testcases/cluster_role/cluster_role_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all cluster_roles @{cluster_roles_list}= List Cluster Role @{cluster_roles_name_list}= Filter Names ${cluster_roles_list} Log \nCluster_role ${cluster_roles_name_list}: console=True List all cluster_role_bindings @{cluster_role_bindings_list}= List Cluster Role Binding @{cluster_role_bindings_names_list}= Filter Names ${cluster_role_bindings_list} Log \nCluster_role_binding ${cluster_role_bindings_names_list}: console=True ================================================ FILE: testcases/configmap/configmap.robot ================================================ *** Settings *** Resource ./configmap_kw.robot *** Test Cases *** Configmap test case example [Tags] grafana List all configmaps in namespace default List all key value pairs in configmap grafana default Configmap by label [Tags] grafana List all configmaps in namespace default app=grafana ================================================ FILE: testcases/configmap/configmap_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all configmaps in namespace [Arguments] ${namespace} ${label}=${EMPTY} @{namespace_configmaps}= List namespaced config map by pattern .* ${namespace} ${label} Log \nConfigmaps in namespace ${namespace}: console=True FOR ${configmap} IN @{namespace_configmaps} Log ${configmap.metadata.name} console=True END List all key value pairs in configmap [Arguments] ${configmap_name} ${namespace} @{namespace_configmaps}= List namespaced config map by pattern ^${configmap_name}$ ${namespace} Log \nList of key value pairs in configmap ${configmap_name}: console=True FOR ${configmap} IN @{namespace_configmaps} Log key value pairs ${configmap.data} END Log key value pairs [Arguments] ${configmap_data} FOR ${key} ${value} IN &{configmap_data} Log ${key} = ${value} console=True END ================================================ FILE: testcases/connect_GKE_clusters.robot ================================================ *** Settings *** Library OperatingSystem Documentation To have a valid kube_config file for a Google Cloud hosted GKE cluster, ... you need to run "gcloud auth activate-service-account". ... gcloud saves an access token with expiration time in your kube_config file. ... By running "gcloud auth activate-service-account" this token gets renewed. *** Keywords *** Connect to GKE cluster Run gcloud auth activate-service-account ${GCLOUD_SERVICE_ACCOUNT} --key-file=${GCLOUD_SA_CREDENTIALS_FILE} Import Library KubeLibrary kube_config=${kube_config} ================================================ FILE: testcases/cronjob/cronjob.robot ================================================ *** Settings *** Resource ./cronjob_kw.robot *** Test Cases *** Job test case example [Tags] other List all cron jobs in namespace kubelib-tests Jobs by label [Tags] other List cron jobs with label ${cron_job_name} kubelib-tests TestLabel=mytestlabel Working on Cron Job [Tags] other List all cron jobs in namespace kubelib-tests Edit obtained cron job test-cronjob Create new cron job in namespace kubelib-tests Delete created cron job in namespace test-cronjob kubelib-tests ================================================ FILE: testcases/cronjob/cronjob_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all cron jobs in namespace [Arguments] ${namespace} @{namespace_cron_jobs}= List Namespaced Cron Job ${namespace} Log \nCron Jobs in namespace ${namespace}: console=True Length Should Be ${namespace_cron_jobs} 1 FOR ${cron_job} IN @{namespace_cron_jobs} ${cronjob_details}= Read Namespaced Cron Job ${cron_job.metadata.name} ${namespace} Log ${cronjob_details.metadata.name} console=True Set Global Variable ${cron_job_name} ${cronjob_details.metadata.name} Set Global Variable ${cron_job} ${cronjob_details} END List cron jobs with label [Arguments] ${cron_job_name} ${namespace} ${label} @{namespace_cron_jobs}= List Namespaced Cron Job ${namespace} ${label} Log \nList labels in cron job ${cron_job_name}: console=True Length Should Be ${namespace_cron_jobs} 1 FOR ${cron_job} IN @{namespace_cron_jobs} ${cron_job_details}= Read Namespaced Cron Job ${cron_job.metadata.name} ${namespace} ${label_key}= Fetch From Left ${label} = ${label_value}= Fetch From Right ${label} = Log Labels in ${cron_job_details.metadata.labels} console=True Dictionary Should Contain Item ${cron_job_details.metadata.labels} ${label_key} ${label_value} ... msg=Expected labels do not match. END Edit obtained cron job [Arguments] ${cron_job_name} ${cron_job.metadata.name}= Set Variable ${cron_job_name} ${cron_job.metadata.resource_version}= Set Variable ${None} Set Global Variable ${new_cron_job} ${cron_job} Create new cron job in namespace [Arguments] ${namespace} Log \nCreate new cron job in namespace ${namespace} console=True ${new_cj}= Create Namespaced Cron Job ${namespace} ${new_cron_job} Log ${new_cj} console=True Delete created cron job in namespace [Arguments] ${cron_job_name} ${namespace} Log \nDeletee cron job in namespace ${namespace} console=True ${status}= Delete Namespaced Cron Job ${cron_job_name} ${namespace} Log ${status} ================================================ FILE: testcases/custom_objects/ambassador_crds.robot ================================================ *** Settings *** Library KubeLibrary Documentation These are example test cases to check custom resource definitions created by Ambassador. ... Ambassador is on open source API gateway solution. The most common CRDs created by ambassador ... are Mappings and Hosts, which control the routing of API requests to different services ... running in your cluster. ... https://www.getambassador.io/products/api-gateway/ ... More details on Ambassador Mappings: ... https://www.getambassador.io/docs/pre-release/topics/using/intro-mappings/#introduction-to-the-mapping-resource *** Test Cases *** Get details for all ambassador mappings ${listed_mappings}= List Cluster Custom Object getambassador.io v2 mappings FOR ${mapping} IN @{listed_mappings['items']} Log Mapping name: ${mapping}[metadata][name] console=True Log Mapping namespace: ${mapping}[metadata][namespace] console=True Log Mapping prefix: ${mapping}[spec][prefix] console=True Log Mapping service: ${mapping}[spec][service] console=True ${status}= Run Keyword And Ignore Error Log Mapping status: ${mapping}[status][state] console=True Run Keyword If '''FAIL''' in '''${status}''' Log ! Mapping ${mapping}[metadata][name] is not running console=True Log --------------------------------------------------- console=True END Get details for all ambassador hosts ${listed_hosts}= List Cluster Custom Object getambassador.io v2 hosts FOR ${host} IN @{listed_hosts['items']} Log Host name: ${host}[metadata][name] console=True Log Host namespace: ${host}[metadata][namespace] console=True Log Host hostname: ${host}[spec][hostname] console=True Log Host status: ${host}[status][state] console=True Log --------------------------------------------------- console=True END ================================================ FILE: testcases/custom_objects/custom_objects.robot ================================================ *** Settings *** Library KubeLibrary *** Test Cases *** Get List Of Cluster Custom Objects [Tags] smoke ${prio_classes}= List Cluster Custom Object scheduling.k8s.io v1 priorityclasses # Log To Console ${prio_classes} Should Be Equal As Strings ${prio_classes}[kind] PriorityClassList Get Cluster Custom Object [Tags] smoke ${prio_class}= Get Cluster Custom Object scheduling.k8s.io v1 priorityclasses system-node-critical Should Be Equal As Strings ${prio_class}[metadata][name] system-node-critical Should Be Equal As Strings ${prio_class}[kind] PriorityClass Get Namespaced Custom Object [Tags] smoke ${fo}= Get Namespaced Custom Object discovery.k8s.io v1 default endpointslices kubernetes Should Be Equal As Strings ${fo}[metadata][name] kubernetes Should Be Equal As Strings ${fo}[addressType] IPv4 Get List Of Namespaced Custom Objects [Tags] smoke ${fo}= List Namespaced Custom Object discovery.k8s.io v1 default endpointslices FOR ${object} IN @{fo['items']} Should Be Equal As Strings ${object}[addressType] IPv4 END ================================================ FILE: testcases/daemonset/daemonsets.robot ================================================ *** Settings *** Resource ./daemonsets_kw.robot *** Test Cases *** Daemonsets test case example [Tags] other List all daemonsets kubelib-tests List daemonsets filtered by label kubelib-tests TestLabel=mytestlabel ================================================ FILE: testcases/daemonset/daemonsets_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all daemonsets [Arguments] ${namespace} @{namespace_daemonsets}= List Namespaced Daemon Set ${namespace} Length Should Be ${namespace_daemonsets} 1 Log \nDaemonsets ${namespace_daemonsets}: console=True FOR ${daemonset} IN @{namespace_daemonsets} Wait Until Keyword Succeeds 1min 5sec Check Daemonset readiness ${daemonset.metadata.name} ${namespace} END Check Daemonset readiness [Arguments] ${daemonset} ${namespace} ${daemonset_details}= Read Namespaced Daemon Set ${daemonset} ${namespace} Log \nDaemonset - ${daemonset}: console=True Log \n\tDesired Number Scheduled : ${daemonset_details.status.desired_number_scheduled} console=True Log \n\tNumber Ready : ${daemonset_details.status.number_ready} : console=True Should be True ${daemonset_details.status.number_ready} > 0 Should be equal ${daemonset_details.status.desired_number_scheduled} ${daemonset_details.status.number_ready} List daemonsets filtered by label [Arguments] ${namespace} ${label} @{namespace_daemonsets}= List Namespaced Daemon Set ${namespace} ${label} Length Should Be ${namespace_daemonsets} 1 Log \nDaemonsets with label ${label} ${namespace_daemonsets}: console=True FOR ${daemonset} IN @{namespace_daemonsets} ${daemonset_details}= Read Namespaced Daemon Set ${daemonset.metadata.name} ${namespace} Log \nDaemonset - ${daemonset}: console=True Log \n\tDesired Number Scheduled : ${daemonset_details.status.desired_number_scheduled} console=True Log \n\tNumber Ready : ${daemonset_details.status.number_ready} : console=True Should be equal ${daemonset_details.status.desired_number_scheduled} ${daemonset_details.status.number_ready} END ================================================ FILE: testcases/deployment/deployment.robot ================================================ *** Settings *** Resource ./deployment_kw.robot *** Test Cases *** Deployment test case example [Tags] grafana List all deployments in namespace default Show Grafana Deployment Assert Replica Status ================================================ FILE: testcases/deployment/deployment_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all deployments in namespace [Arguments] ${namespace} ${label}=${EMPTY} @{namespace_deployments}= List Namespaced Deployment By Pattern .* ${namespace} ${label} Log \nDeployments in namespace ${namespace}: console=True FOR ${deployment} IN @{namespace_deployments} Log ${deployment.metadata.name} console=True END Show Grafana Deployment @{namespace_deployments}= List Namespaced Deployment By Pattern grafana default FOR ${deployment} IN @{namespace_deployments} Should be Equal ${deployment.metadata.name} grafana Set Global Variable ${DEPLOYMENT} ${deployment} Log \nDeployment ${deployment.metadata.name}: console=True Log ${deployment} console=True Log \n console=True END Assert Replica Status Should be Equal ${DEPLOYMENT.status.available_replicas} ${DEPLOYMENT.status.replicas} ... msg=Available replica count (${DEPLOYMENT.status.available_replicas}) doesn't match current replica count (${DEPLOYMENT.status.replicas}) ================================================ FILE: testcases/dynamic_client/dynamic_client.robot ================================================ *** Settings *** Resource ./dynamic_client_kw.robot *** Test Cases *** Dynamic client test case example [Tags] dynamic-client other ${resources}= discover resources default Log To Console ${resources} Dynamic client test case example 2 [Tags] dynamic-client other ${conf}= read conf testcases/dynamic_client/resources/pod.yaml create pod ${conf} sleep 5 seconds ${pods}= get specific pod default app=myapp ${metadata}= Get From Dictionary ${conf} metadata ${patched_labels}= Create Dictionary app=myapp tested=true ${patched_metadata}= Set To Dictionary ${metadata} labels ${patched_labels} ${patched_pod}= Set To Dictionary ${conf} metadata ${patched_metadata} patch pod ${patched_pod} sleep 5 seconds ${pods}= get specific pod default tested=true Should Not Be Empty ${pods.items} ${pods}= get specific pod default tested=false Should Be Empty ${pods.items} [Teardown] delete pod default myapp-pod Dynamic client test case example 3 [Tags] dynamic-client other ${conf}= read conf testcases/dynamic_client/resources/svc.yaml create svc ${conf} sleep 5 seconds ${svc}= get specific svc default myservice ${new_selector}= Create Dictionary app=svc-lookup ${new_spec}= Set To Dictionary ${conf}[spec] selector ${new_selector} ${new_spec}= Set To Dictionary ${new_spec} clusterIP ${svc.spec.clusterIP} ${new_conf}= Set To Dictionary ${conf} spec ${new_spec} ${new_metadata}= Create Dictionary name=myservice namespace=default resourceVersion=${svc.metadata.resourceVersion} ${new_conf}= Set To Dictionary ${new_conf} metadata ${new_metadata} replace svc ${new_conf} ${conf}= read conf testcases/dynamic_client/resources/svc_lookup.yaml create pod ${conf} sleep 5 seconds [Teardown] Run Keywords delete pod default svc-lookup AND delete svc default myservice Dynamic client test case example create Pod with generated name [Tags] dynamic-client other prerelease ${conf}= read conf testcases/dynamic_client/resources/pod_generated_name.yaml ${pod}= create pod ${conf} sleep 5 seconds ${pod_dict}= Call Method ${pod} to_dict ${pod_name}= Get From Dictionary ${pod_dict['metadata']} name ${created_pod}= Get api_version=v1 kind=Pod namespace=default name=${pod_name} Should Not Be Empty ${created_pod} [Teardown] delete pod default ${pod_name} ================================================ FILE: testcases/dynamic_client/dynamic_client_kw.robot ================================================ *** Settings *** Library BuiltIn Library yaml Library OperatingSystem Library Collections # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** discover resources [Arguments] ${namespace} ${deployments}= get resource names Deployment apps/v1 ${namespace} ${statefulsets}= get resource names StatefulSet apps/v1 ${namespace} ${cronjobs}= get resource names CronJob batch/v1 ${namespace} ${secrets}= get resource names Secret v1 ${namespace} ${configmaps}= get resource names ConfigMap v1 ${namespace} ${found}= BuiltIn.evaluate ${deployments} + ${statefulsets} + ${cronjobs} + ${secrets} + ${configmaps} [return] ${found} get resource names [Arguments] ${kind} ${api_version} ${namespace} ${resource_list}= KubeLibrary.get kind=${kind} api_version=${api_version} namespace=${namespace} @{names}= Create List FOR ${resource} IN @{resource_list.items} Append To List ${names} ${resource.metadata.name} END [return] ${names} create pod [Arguments] ${conf} ${new_pod}= KubeLibrary.create api_version=v1 kind=Pod body=${conf} [return] ${new_pod} get specific pod [Arguments] ${namespace} ${label_selector} ${pods}= KubeLibrary.get api_version=v1 kind=Pod namespace=${namespace} label_selector=${label_selector} [return] ${pods} patch pod [Arguments] ${pod} KubeLibrary.patch api_version=v1 kind=Pod body=${pod} delete pod [Arguments] ${namespace} ${name} KubeLibrary.delete api_version=v1 kind=Pod name=${name} namespace=${namespace} replace svc [Arguments] ${conf} KubeLibrary.replace api_version=v1 kind=Service body=${conf} read conf [Arguments] ${path} ${stream}= Get Binary File ${path} ${conf}= yaml.Safe Load ${stream} [return] ${conf} create svc [Arguments] ${conf} KubeLibrary.create api_version=v1 kind=Service body=${conf} delete svc [Arguments] ${namespace} ${name} KubeLibrary.delete api_version=v1 kind=Service name=${name} namespace=${namespace} get specific svc [Arguments] ${namespace} ${name} ${svc}= KubeLibrary.get api_version=v1 kind=Service name=${name} namespace=${namespace} [return] ${svc} ================================================ FILE: testcases/dynamic_client/resources/pod.yaml ================================================ apiVersion: v1 kind: Pod metadata: namespace: default name: myapp-pod labels: app: myapp tested: "false" spec: restartPolicy: OnFailure containers: - name: myapp-container image: busybox:1.28 command: ['sh', '-c', 'while true; do echo The app is running!; sleep 5; done'] ================================================ FILE: testcases/dynamic_client/resources/pod_generated_name.yaml ================================================ apiVersion: v1 kind: Pod metadata: namespace: default generateName: myapp-pod- labels: app: myapp tested: "false" spec: restartPolicy: OnFailure containers: - name: myapp-container image: busybox:1.28 command: ['sh', '-c', 'while true; do echo The app is running!; sleep 5; done'] ================================================ FILE: testcases/dynamic_client/resources/svc.yaml ================================================ apiVersion: v1 kind: Service metadata: name: myservice namespace: default spec: selector: app: dummy ports: - protocol: TCP port: 80 targetPort: 5000 ================================================ FILE: testcases/dynamic_client/resources/svc_lookup.yaml ================================================ apiVersion: v1 kind: Pod metadata: namespace: default name: svc-lookup labels: app: svc-lookup spec: restartPolicy: OnFailure containers: - name: svc-lookup image: busybox:1.28 command: ['sh', '-c', 'until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done'] ports: - containerPort: 5000 ================================================ FILE: testcases/exec/exec.robot ================================================ *** Settings *** Resource ./exec_kw.robot Force Tags exec other *** Variables *** ${string} Hello ${POD_NAME_EXEC} myapp-pod ${CONTAINER_NAME} myapp-container *** Test Cases *** Exec test case example ${command} Create List /bin/sh -c echo ${string} Set Pod Name For Namespace ${stdout} Get Namespaced Pod Exec name=${POD_NAME_EXEC} ... namespace=${namespace} ... argv_cmd=${command} Should Be Equal ${stdout} ${string} ... msg=Stdout should be ${string} not ${stdout}! Exec multiple commands ${command} Create List /bin/sh -c echo 1 && echo 2 && echo 3 Set Pod Name For Namespace ${stdout} Get Namespaced Pod Exec name=${POD_NAME_EXEC} ... namespace=${namespace} ... argv_cmd=${command} Should Be Equal ${stdout} 1\n2\n3 ... msg=Stdout should be ${string} not ${stdout}! Exec command for specific container ${command} Create List /bin/sh -c echo ${string} Set Pod Name For Namespace ${stdout} Get Namespaced Pod Exec name=${POD_NAME_EXEC} ... namespace=${namespace} ... argv_cmd=${command} ... container=${CONTAINER_NAME} Should Be Equal ${stdout} ${string} ... msg=Stdout should be ${string} not ${stdout}! Exec wrong command syntax Set Pod Name For Namespace Run Keyword And Expect Error STARTS: TypeError: ... Get Namespaced Pod Exec name=${POD_NAME_EXEC} ... namespace=${namespace} ... argv_cmd=echo ${string} ================================================ FILE: testcases/exec/exec_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Variables *** ${namespace} default ${POD_NAME} *** Keywords *** Set Pod Name For namespace ${namespace_pods} List Namespaced POD By Pattern .* ${namespace} ${namespace_pods_names} Filter Names ${namespace_pods} Set Test Variable ${POD_NAME} ${namespace_pods_names}[0] ================================================ FILE: testcases/grafana/demo-UI-test.robot ================================================ **** Settings *** Library KubeLibrary Library Browser Library Collections Documentation This is an example test case to demo the abilities of the KubeLibrary. ... This test will assert if all Kubernetes objects of a Grafana installation ... were successfully install to the cluster. ... ... Afterwards it will execute a UI test to perfom the login to the Grafana ... dashboard with the url, username and password obtained from Kubernetes. ... ... This tests requires an installation of Grafana as described here: ... https://github.com/devopsspiral/KubeLibrary#grafana-tests *** Test Cases *** Grafana is ready to be tested [Documentation] Verifying all objects created by the helm deployment Check Grafana Deployment Assert Replica Status Grafana pods are running Check Grafana service Check Grafana secrets Check Grafana serviceaccounts Obtain Grafana URL [Documentation] Obtaining the url and port from the service object Get URL and port from service Obtain Grafana login credentials [Documentation] Obtaining username and password from the secrets object Read grafana secrets Successful Login to Grafana [Documentation] Login to the Grafana UI dashboard Login to Grafana *** Keywords *** Check Grafana Deployment @{namespace_deployments}= List Namespaced Deployment By Pattern grafana default Length Should Be ${namespace_deployments} 1 FOR ${deployment} IN @{namespace_deployments} Should be Equal ${deployment.metadata.name} grafana Set Global Variable ${DEPLOYMENT} ${deployment} END Assert Replica Status Should be Equal ${DEPLOYMENT.status.available_replicas} ${DEPLOYMENT.status.replicas} ... msg=Available replica count (${DEPLOYMENT.status.available_replicas}) doesn't match current replica count (${DEPLOYMENT.status.replicas} Grafana pods are running Wait Until Keyword Succeeds 1min 5sec Check grafana pod status Check Grafana pod status @{namespace_pods}= Get Pod Names In Namespace grafana default Length Should Be ${namespace_pods} 1 FOR ${pod} IN @{namespace_pods} ${status}= Get Pod Status In Namespace ${pod} default Should Be True '${status}'=='Running' END Check Grafana service ${sevice_details}= Get Service Details In Namespace grafana default Dictionary Should Contain Item ${sevice_details.metadata.labels} app.kubernetes.io/name grafana ... msg=Expected labels do not match. Should Be Equal ${sevice_details.spec.type} LoadBalancer ... msg=Expected service type does not match. Check Grafana secrets @{namespace_secrets}= List Namespaced Secret By Pattern grafana default Length Should Be ${namespace_secrets} 3 Check Grafana serviceaccounts @{namespace_service_accounts}= List Namespaced Service Account By Pattern grafana default Length Should Be ${namespace_service_accounts} 2 FOR ${service_account} IN @{namespace_service_accounts} Dictionary Should Contain Item ${service_account.metadata.labels} app.kubernetes.io/name grafana END Get URL and port from service ${sevice_details}= Get Service Details In Namespace grafana default Set Global Variable ${URL} ${sevice_details.status.load_balancer.ingress[0].ip} Set Global Variable ${PORT} ${sevice_details.spec.ports[0].port} Read grafana secrets @{namespace_secrets}= List Namespaced Secret By Pattern ^grafana$ default Length Should Be ${namespace_secrets} 1 ${GRAFANA_USER}= Evaluate base64.b64decode($namespace_secrets[0].data["admin-user"]) modules=base64 Set Global Variable ${GRAFANA_USER} ${GRAFANA_USER} ${GRAFANA_PASSWORD}= Evaluate base64.b64decode($namespace_secrets[0].data["admin-password"]) modules=base64 Set Global Variable ${GRAFANA_PASSWORD} ${GRAFANA_PASSWORD} Login to Grafana New Browser chromium headless=${False} New Page http://${URL}:${PORT} Wait Until Network Is Idle Sleep 20 #For live demo purposes Fill Text //input[@name='user'] ${GRAFANA_USER} Fill Text //input[@name='password'] ${GRAFANA_PASSWORD} Click button Wait For Response /api/dashboards/home Wait Until Network Is Idle Get Title matches Home - Grafana Get Text //h1 matches Welcome to Grafana Sleep 20 #For live demo purposes ================================================ FILE: testcases/grafana/values.yaml ================================================ service: type: LoadBalancer port: 3000 persistence: enabled: true type: pvc size: 1Gi podAnnotations: kubelibrary: testing resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" ================================================ FILE: testcases/healthcheck/healthcheck.robot ================================================ *** Settings *** Library Collections Resource ./healthcheck_kw.robot *** Test Cases *** Healthcheck [Tags] other Healthcheck Health API Reports Ok [Tags] cluster smoke ${response}= Query Health API verbose=False Should Be equal As Strings ${response}[0] ok Health API Reports Checks Passed With Verbose [Tags] cluster smoke ${response}= Query Health API # healthz < 1.20, livez >=1.20 Should Match Regexp ${response}[0] (livez|healthz) check passed Health API Reports Ok For informer-sync [Tags] cluster smoke ${response}= Query Health API /readyz/informer-sync Should Be equal As Strings ${response}[0] ok Health API Reports Ok For shutdown [Tags] cluster smoke ${response}= Query Health API /readyz/shutdown Should Be equal As Strings ${response}[0] ok Health API Reports Ok For ping [Tags] cluster smoke [Template] Health API Template ping ok Health API Reports Ok For log [Tags] cluster smoke [Template] Health API Template log ok Health API Reports Ok For etcd [Tags] cluster smoke [Template] Health API Template etcd ok Health API Reports Ok For poststarthook/start-kube-apiserver-admission-initializer [Tags] cluster [Template] Health API Template poststarthook/start-kube-apiserver-admission-initializer ok Health API Reports Ok For poststarthook/generic-apiserver-start-informers [Tags] cluster smoke [Template] Health API Template poststarthook/generic-apiserver-start-informers ok Health API Reports Ok For poststarthook/max-in-flight-filter [Tags] cluster [Template] Health API Template poststarthook/max-in-flight-filter ok Health API Reports Ok For poststarthook/start-apiextensions-informers [Tags] cluster smoke [Template] Health API Template poststarthook/start-apiextensions-informers ok Health API Reports Ok For poststarthook/start-apiextensions-controllers [Tags] cluster smoke [Template] Health API Template poststarthook/start-apiextensions-controllers ok Health API Reports Ok For poststarthook/crd-informer-synced [Tags] cluster smoke [Template] Health API Template poststarthook/crd-informer-synced ok Health API Reports Ok For poststarthook/bootstrap-controller [Tags] cluster smoke [Template] Health API Template poststarthook/bootstrap-controller ok Health API Reports Ok For poststarthook/scheduling/bootstrap-system-priority-classes [Tags] cluster smoke [Template] Health API Template poststarthook/scheduling/bootstrap-system-priority-classes ok Health API Reports Ok For poststarthook/start-cluster-authentication-info-controller [Tags] cluster smoke [Template] Health API Template poststarthook/start-cluster-authentication-info-controller ok Health API Reports Ok For poststarthook/aggregator-reload-proxy-client-cert [Tags] cluster smoke [Template] Health API Template poststarthook/aggregator-reload-proxy-client-cert ok Health API Reports Ok For poststarthook/start-kube-aggregator-informers [Tags] cluster smoke [Template] Health API Template poststarthook/start-kube-aggregator-informers ok Health API Reports Ok For poststarthook/apiservice-registration-controller [Tags] cluster smoke [Template] Health API Template poststarthook/apiservice-registration-controller ok #Health API Reports Ok For poststarthook/apiservice-status-available-controller # [Tags] cluster smoke # [Template] Health API Template # poststarthook/apiservice-status-available-controller ok Health API Reports Ok For poststarthook/kube-apiserver-autoregistration [Tags] cluster smoke [Template] Health API Template poststarthook/kube-apiserver-autoregistration ok Health API Reports Ok For autoregister-completion [Tags] cluster smoke [Template] Health API Template autoregister-completion ok Health API Reports Ok For poststarthook/apiservice-openapi-controller [Tags] cluster smoke [Template] Health API Template poststarthook/apiservice-openapi-controller ok ================================================ FILE: testcases/healthcheck/healthcheck_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution #Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development Library ../../src/KubeLibrary/KubeLibrary.py *** Keywords *** Healthcheck @{RESPONSE}= Get Healthcheck @{ENDPOINTS} = Split String ${RESPONSE}[0] \n FOR ${ELEMENT} IN @{ENDPOINTS} Should Be True "ok" or "healthz check passed" in """${ELEMENT}""" END Should Be Equal As Strings ${RESPONSE}[1] 200 Query Health API [Arguments] ${endpoint}=/livez ${verbose}=True ${response}= get_healthcheck ${endpoint} ${verbose} Should Be Equal As integers ${response}[1] 200 [Return] ${response} Health API Template [Documentation] Checks both /readyz/${endpoint} and /livez/${endpoint} [Arguments] ${endpoint} ${expected} ${readyz}= Query Health API /readyz/${endpoint} ${livez}= Query Health API /livez/${endpoint} Should Match Regexp ${readyz}[0] ${expected} Should Match Regexp ${livez}[0] ${expected} ================================================ FILE: testcases/horizontalPodAutoscaler/hpa.robot ================================================ *** Settings *** Resource ./hpa_kw.robot *** Test Cases *** List Horizontal Pod Autoscalers in namespace [Tags] other prerelaese List Horizontal Pod Autoscalers by namespace kubelib-tests ================================================ FILE: testcases/horizontalPodAutoscaler/hpa_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List Horizontal Pod Autoscalers by namespace [Arguments] ${namespace} @{namespace_hpas}= List Namespaced Horizontal Pod Autoscaler ${namespace} FOR ${hpa} IN @{namespace_hpas} ${hpa_details}= Read Namespaced Horizontal Pod Autoscaler ${hpa.metadata.name} ${namespace} Should be Equal as Strings ${hpa_details.spec.max_replicas} 5 Should be Equal as Strings ${hpa_details.spec.min_replicas} 1 Should be Equal as Strings ${hpa_details.spec.target_cpu_utilization_percentage} 50 END ================================================ FILE: testcases/ingress/ingress.robot ================================================ *** Settings *** Resource ./ingress_kw.robot *** Test Cases *** Ingresses by label [Tags] other List ingresses by label kubelib-tests app.kubernetes.io/instance=kubelib-test ================================================ FILE: testcases/ingress/ingress_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List ingresses by label [Arguments] ${namespace} ${label} @{namespace_ingresses}= List Namespaced Ingress ${namespace} ${label} Length Should Be ${namespace_ingresses} 1 FOR ${ingress} IN @{namespace_ingresses} ${ingress_details}= Read Namespaced Ingress ${ingress.metadata.name} ${namespace} ${label_key}= Fetch From Left ${label} = ${label_value}= Fetch From Right ${label} = Dictionary Should Contain Item ${ingress_details.metadata.labels} ${label_key} ${label_value} ... msg=Expected labels do not match. Log Ingress Host Url: ${ingress_details.spec.rules[0].host} console=True END ================================================ FILE: testcases/job/job.robot ================================================ *** Settings *** Resource ./job_kw.robot *** Test Cases *** Job test case example [Tags] other List all jobs in namespace kubelib-tests List labels of job ${job_name} kubelib-tests Get pod created by job ${job_name} kubelib-tests Jobs by label [Tags] other List jobs with label .* kubelib-tests TestLabel=mytestlabel ================================================ FILE: testcases/job/job_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all jobs in namespace [Arguments] ${namespace} @{namespace_jobs}= List Namespaced Job By Pattern .* ${namespace} Log \nJobs in namespace ${namespace}: console=True FOR ${job} IN @{namespace_jobs} Log ${job.metadata.name} console=True Set Global Variable ${job_name} ${job.metadata.name} END List labels of job [Arguments] ${job_name} ${namespace} @{namespace_jobs}= List Namespaced Job By Pattern ^${job_name}$ ${namespace} Log \nList labels in job ${job_name}: console=True FOR ${job} IN @{namespace_jobs} Log Labels in ${job.metadata.labels} console=True Dictionary Should Contain Item ${job.metadata.labels} TestLabel mytestlabel ... msg=Expected labels do not match. END List jobs with label [Arguments] ${job_name} ${namespace} ${label} @{namespace_jobs}= List Namespaced Job By Pattern ${job_name} ${namespace} ${label} Log \nList labels in job ${job_name}: console=True FOR ${job} IN @{namespace_jobs} Log Labels in ${job.metadata.labels} console=True Dictionary Should Contain Item ${job.metadata.labels} TestLabel mytestlabel ... msg=Expected labels do not match. END Get pod created by job [Arguments] ${job_name} ${namespace} @{namespace_pods}= List Namespaced Job By Pattern ${job_name} ${namespace} FOR ${pod} IN @{namespace_pods} Log \nList labels in pod ${pod.metadata.name}: console=True Log ${pod.metadata.labels} console=True Dictionary Should Contain Item ${pod.metadata.labels} TestLabel mytestlabel ... msg=Could not find job name label. END ================================================ FILE: testcases/namespace/namespace.robot ================================================ *** Settings *** Resource ./namespace_kw.robot *** Test Cases *** Namespace test case example [Tags] other List all namespaces List namespaces filtered by label ================================================ FILE: testcases/namespace/namespace_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all namespaces @{namespaces_list}= List Namespace @{namespace_names}= Filter Names ${namespaces_list} Log \nNamespaces ${namespace_names}: console=True List namespaces filtered by label @{namespaces_list}= List Namespace label_selector=test=test @{namespace_names}= Filter Names ${namespaces_list} Log \nNamespaces with label test=test ${namespace_names}: console=True ================================================ FILE: testcases/pod/pod.robot ================================================ *** Settings *** Resource ./pod_kw.robot *** Variables *** ${KLIB_POD_PATTERN} %{KLIB_POD_PATTERN} ${KLIB_POD_NAMESPACE} %{KLIB_POD_NAMESPACE} ${KLIB_POD_REPLICAS} %{KLIB_POD_REPLICAS=1} ${KLIB_POD_TIMEOUT} %{KLIB_POD_TIMEOUT=2min} ${KLIB_POD_RETRY_INTERVAL} %{KLIB_POD_RETRY_INTERVAL=5sec} ${KLIB_POD_LABELS} %{KLIB_POD_LABELS='Labels missing!'} ${KLIB_POD_ANNOTATIONS} %{KLIB_POD_ANNOTATIONS='Annotations missing!'} ${KLIB_RESOURCE_REQUESTS_CPU} %{KLIB_RESOURCE_REQUESTS_CPU='Resource requests missing!'} ${KLIB_RESOURCE_REQUESTS_MEMORY} %{KLIB_RESOURCE_REQUESTS_MEMORY='Resource requests missing!'} ${KLIB_RESOURCE_LIMITS_CPU} %{KLIB_RESOURCE_LIMITS_CPU='Resource limits missing!'} ${KLIB_RESOURCE_LIMITs_MEMORY} %{KLIB_RESOURCE_LIMITS_MEMORY='Resource limits missing!'} ${KLIB_ENV_VARS} %{KLIB_ENV_VARS='Env vars missing!'} ${LOGS_SINCE} 1000 *** Test Cases *** Pod images has correct version [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then all pods containers are using "grafana/grafana" image Pod has enough replicas [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods have "${KLIB_POD_REPLICAS}" replicas Pod has not been restarted [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods containers were not restarted Pod have correct labels [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods have labels "${KLIB_POD_LABELS}" Pod has correct annotations [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods have annotations "${KLIB_POD_ANNOTATIONS}" Pod has correct limits/requests [Tags] grafana Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods containers have resource requests cpu "${KLIB_RESOURCE_REQUESTS_CPU}" Then pods containers have resource requests memory "${KLIB_RESOURCE_REQUESTS_MEMORY}" Then pods containers have resource limits cpu "${KLIB_RESOURCE_LIMITS_CPU}" Then pods containers have resource limits memory "${KLIB_RESOURCE_LIMITS_MEMORY}" Pod has correct env variables Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then pods containers have env variables "${KLIB_ENV_VARS}" Logs of pod are available [Tags] other Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then logs of pod can be retrived And logs contain expected string Logs of pod since 1000s are available [Tags] other Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" Then logs of pod can be retrived since "${LOGS_SINCE}" And logs contain expected string List pods by label [Tags] other Given waited for pods matching "${KLIB_POD_PATTERN}" in namespace "${KLIB_POD_NAMESPACE}" to be READY When getting pods matching label "${KLIB_POD_LABELS}" in namespace "${KLIB_POD_NAMESPACE}" Then pods have labels "${KLIB_POD_LABELS}" ================================================ FILE: testcases/pod/pod_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** waited for pods matching "${name_pattern}" in namespace "${namespace}" to be READY Wait Until Keyword Succeeds ${KLIB_POD_TIMEOUT} ${KLIB_POD_RETRY_INTERVAL} pod "${name_pattern}" status in namespace "${namespace}" is READY pod "${name_pattern}" status in namespace "${namespace}" is READY @{namespace_pods}= list_namespaced_pod_by_pattern ${name_pattern} ${namespace} @{namespace_pods_names}= Filter Names ${namespace_pods} ${num_of_pods}= Get Length ${namespace_pods_names} Should Be True ${num_of_pods} >= 1 No pods matching "${name_pattern}" found FOR ${pod} IN @{namespace_pods_names} ${status}= read_namespaced_pod_status ${pod} ${namespace} ${conditions}= Filter by Key ${status.conditions} type Ready Should Be True '${conditions[0].status}'=='True' END getting pods matching "${name_pattern}" in namespace "${namespace}" @{namespace_pods}= list_namespaced_pod_by_pattern ${name_pattern} ${namespace} Set Test Variable ${namespace_pods} getting pods matching label "${label}" in namespace "${namespace}" @{namespace_pods}= list_namespaced_pod_by_pattern .* ${namespace} label_selector=${label} Set Test Variable ${namespace_pods} ${label_key}= Fetch From Left ${KLIB_POD_LABELS} = ${label_value}= Fetch From Right ${KLIB_POD_LABELS} = Set Test Variable ${KLIB_POD_LABELS} {"${label_key}": "${label_value}"} all pods containers are using "${container_image}" image @{containers}= filter_pods_containers_by_name ${namespace_pods} .* @{containers_images}= filter_containers_images ${containers} FOR ${item} IN @{containers_images} Should Contain ${item} ${container_image} END pods have "${pod_replicas}" replicas ${count}= Get Length ${namespace_pods} Should Be True ${count} == ${pod_replicas} pods containers were not restarted @{containers_statuses}= filter_pods_containers_statuses_by_name ${namespace_pods} .* FOR ${container_status} IN @{containers_statuses} Should Be True ${container_status.restart_count} == 0 END pods have labels "${pod_labels}" FOR ${pod} IN @{namespace_pods} ${assertion}= assert_pod_has_labels ${pod} ${pod_labels} Should Be True ${assertion} END pods have annotations "${pod_annotations}" FOR ${pod} IN @{namespace_pods} ${assertion}= assert_pod_has_annotations ${pod} ${pod_annotations} Should Be True ${assertion} END pods containers have resource requests cpu "${container_resource_requests_cpu}" @{containers}= filter_pods_containers_by_name ${namespace_pods} .* @{containers_resources}= filter_containers_resources ${containers} FOR ${item} IN @{containers_resources} Should Be Equal As Strings ${item.requests['cpu']} ${container_resource_requests_cpu} END pods containers have resource requests memory "${container_resource_requests_memory}" @{containers}= filter_pods_containers_by_name ${namespace_pods} .* @{containers_resources}= filter_containers_resources ${containers} FOR ${item} IN @{containers_resources} Should Be Equal As Strings ${item.requests['memory']} ${container_resource_requests_memory} END pods containers have resource limits cpu "${container_resource_limits_cpu}" @{containers}= filter_pods_containers_by_name ${namespace_pods} .* @{containers_resources}= filter_containers_resources ${containers} FOR ${item} IN @{containers_resources} Should Be Equal As Strings ${item.limits['cpu']} ${container_resource_limits_cpu} END pods containers have resource limits memory "${container_resource_requests_memory}" @{containers}= filter_pods_containers_by_name ${namespace_pods} .* @{containers_resources}= filter_containers_resources ${containers} FOR ${item} IN @{containers_resources} Should Be Equal As Strings ${item.limits['memory']} ${container_resource_requests_memory} END pods containers have env variables "${container_env_vars}" @{containers}= filter_pods_containers_by_name ${namespace_pods} .* FOR ${container} IN @{containers} ${assertion}= assert_container_has_env_vars ${container} ${container_env_vars} Should Be True ${assertion} END logs of pod can be retrived Set Test Variable ${POD_NAME} ${namespace_pods[0].metadata.name} ${pod_logs}= Read namespaced pod log ${POD_NAME} ${KLIB_POD_NAMESPACE} busybox Log ${pod_logs} console=True Set Test Variable ${POD_LOGS} ${pod_logs} logs of pod can be retrived since "${seconds}" Set Test Variable ${POD_NAME} ${namespace_pods[0].metadata.name} ${pod_logs}= Read namespaced pod log ${POD_NAME} ${KLIB_POD_NAMESPACE} busybox since_seconds=${seconds} Log ${pod_logs} console=True Set Test Variable ${POD_LOGS} ${pod_logs} logs contain expected string Should Contain ${POD_LOGS} I am ================================================ FILE: testcases/pvc/pvc.robot ================================================ *** Settings *** Resource ./pvc_kw.robot *** Test Cases *** List Persitent Volume Claims by label [Tags] grafana List pvcs by label default app=grafana List Persitent Volume Claims by pattern [Tags] grafana List pvcs by pattern .* default ================================================ FILE: testcases/pvc/pvc_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List pvcs by label [Arguments] ${namespace} ${label} @{namespace_pvcs}= List namespaced persistent volume claim ${namespace} ${label} @{namespace_pvcs_names}= Filter Names ${namespace_pvcs} Log List of PVCs in Namespace ${namespace} with Label ${label}: @{namespace_pvcs_names} console=True List pvcs by pattern [Arguments] ${pattern} ${namespace} @{namespace_pvcs}= List namespaced persistent volume claim by pattern ${pattern} ${namespace} @{namespace_pvcs_names}= Filter Names ${namespace_pvcs} Log List of PVCs in Namespace ${namespace} with Patter ${pattern}: @{namespace_pvcs_names} console=True ================================================ FILE: testcases/reload-config/reload-config.robot ================================================ *** Settings *** Resource ./reload-config_kw.robot Documentation This test requires two k8s clusters running according to ... the setup description in the README.md *** Test Cases *** Reload config test case example [Tags] reload-config [Documentation] The Keyword "Reload Config" allows the user to ... reload the KubeLibrary with different settings. With this ... one test can validate objects in different clusters. GIVEN Connected to cluster-1 THEN Cluster has namespace test-ns-1 AND Cluster has no namespace test-ns-2 WHEN Connected to cluster-2 THEN Cluster has namespace test-ns-2 AND Cluster has no namespace test-ns-1 Authenticate using bearer token [Tags] reload-config auth.bearer-token [Documentation] Test authentication using brearer token WHEN Connected to cluster-1 using bearer token THEN Cluster has namespace test-ns-1 AND Cluster has no namespace test-ns-2 ================================================ FILE: testcases/reload-config/reload-config_kw.robot ================================================ *** Settings *** Library KubeLibrary %{KUBE_CONFIG1=./cluster1-conf} # For development #Library ../../src/KubeLibrary/KubeLibrary.py *** Variables *** ${KUBE_CONFIG1} %{KUBE_CONFIG1=./cluster1-conf} ${KUBE_CONFIG2} %{KUBE_CONFIG2=./cluster2-conf} *** Keywords *** Connected to cluster-1 Reload Config kube_config=${KUBE_CONFIG1} incluster=False cert_validation=False K8s Api Ping Cluster has namespace [Arguments] ${namespace} @{namespaces_list}= List Namespace @{namespaces_name_list}= Filter Names ${namespaces_list} Should Contain ${namespaces_name_list} ${namespace} Connected to cluster-2 Reload Config kube_config=${KUBE_CONFIG2} incluster=False cert_validation=False K8s Api Ping Connected to cluster-1 using bearer token Reload Config api_url=%{K8S_API_URL} bearer_token=%{K8S_TOKEN} ca_cert=%{K8S_CA_CRT} K8s Api Ping Cluster has no namespace [Arguments] ${namespace} @{namespaces_list}= List Namespace @{namespaces_name_list}= Filter Names ${namespaces_list} Should Not Contain ${namespaces_name_list} ${namespace} ================================================ FILE: testcases/reload-config/sa.yaml ================================================ apiVersion: v1 kind: ServiceAccount metadata: name: mysa labels: source: mysa --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: mysa-admin-binding subjects: - kind: ServiceAccount name: mysa namespace: default roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: Secret metadata: name: mysa-token annotations: kubernetes.io/service-account.name: mysa type: kubernetes.io/service-account-token ================================================ FILE: testcases/replicaset/replicaset.robot ================================================ *** Settings *** Resource ./replicaset_kw.robot *** Test Cases *** Replicaset test case example [Tags] replicaset other List all replicasets in namespace default ================================================ FILE: testcases/replicaset/replicaset_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all replicasets in namespace [Arguments] ${namespace} ${label}=${EMPTY} @{namespace_replicasets}= list_namespaced_replica_set_by_pattern .* ${namespace} ${label} Log \nReplicasets in namespace ${namespace}: console=True FOR ${replicaset} IN @{namespace_replicasets} Log ${replicaset.metadata.name} console=True END ================================================ FILE: testcases/requirements.txt ================================================ robotframework-requests robotframework-kubelibrary ================================================ FILE: testcases/role/role.robot ================================================ *** Settings *** Resource ./role_kw.robot *** Test Cases *** Role test case example [Tags] other List all roles in namespace default List all role bindings in namespace default ================================================ FILE: testcases/role/role_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all roles in namespace [Arguments] ${namespace} @{namespace_roles}= List Namespaced Role ${namespace} Length Should Be ${namespace_roles} 1 Log \nRoles in namespace ${namespace_roles}: console=True List all role bindings in namespace [Arguments] ${namespace} @{namespace_role_bindings}= List Namespaced Role Binding ${namespace} Length Should Be ${namespace_role_bindings} 1 Log \nRole_binding in namespace ${namespace_role_bindings}: console=True ================================================ FILE: testcases/secrets/secret.robot ================================================ *** Settings *** Resource ./secret_kw.robot *** Test Cases *** Secrets test case example [Tags] grafana List all secrets in namespace default Read grafana secrets ================================================ FILE: testcases/secrets/secret_kw.robot ================================================ *** Settings *** # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all secrets in namespace [Arguments] ${namespace} ${label}=${EMPTY} @{namespace_secrets}= List Namespaced Secret By Pattern .* ${namespace} ${label} Log \nSecrets in namespace ${namespace}: console=True FOR ${secret} IN @{namespace_secrets} Log ${secret.metadata.name} console=True END Read grafana secrets @{namespace_secrets}= List Namespaced Secret By Pattern ^grafana$ default Length Should Be ${namespace_secrets} 1 Set Suite Variable ${GRAFANA_USER} ${namespace_secrets[0].data["admin-user"]} ${GRAFANA_USER}= Evaluate base64.b64decode($GRAFANA_USER) modules=base64 Log Grafana user: ${GRAFANA_USER} console=True Set Suite Variable ${GRAFANA_PASSWORD} ${namespace_secrets[0].data["admin-password"]} ${GRAFANA_PASSWORD}= Evaluate base64.b64decode($GRAFANA_PASSWORD) modules=base64 Log Grafana password: ${GRAFANA_PASSWORD} console=True ================================================ FILE: testcases/service/service.robot ================================================ *** Settings *** Resource ./service_kw.robot *** Test Cases *** Services by label [Tags] other List services by label kubelib-tests app.kubernetes.io/instance=kubelib-test ================================================ FILE: testcases/service/service_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary Library String # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List services by label [Arguments] ${namespace} ${label} @{namespace_services}= List Namespaced Service ${namespace} ${label} Log \nServices in namespace ${namespace}: console=True FOR ${service} IN @{namespace_services} Log ${service.metadata.name} console=True ${sevice_details}= Read Namespaced Service ${service.metadata.name} ${namespace} ${label_key}= Fetch From Left ${label} = ${label_value}= Fetch From Right ${label} = Dictionary Should Contain Item ${sevice_details.metadata.labels} ${label_key} ${label_value} ... msg=Expected labels do not match. END ================================================ FILE: testcases/service_account/service_account.robot ================================================ *** Settings *** Resource ./service_account_kw.robot *** Test Cases *** Listing Service Accounts [Tags] other List all service accounts for matching name pattern in namespace .* kubelib-tests List all service accounts for matching name pattern in namespace with label .* kubelib-tests TestLabel=mytestlabel Working on Service Accounts [Tags] other List all service accounts for matching name pattern in namespace default kubelib-tests Edit obtained service account test-sa Create new service account in namespace kubelib-tests Delete created service account in namespace test-sa kubelib-tests ================================================ FILE: testcases/service_account/service_account_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all service accounts for matching name pattern in namespace [Arguments] ${name_pattern} ${namespace} @{namespace_service_account}= List Namespaced Service Account By Pattern ${name_pattern} ${namespace} Log \nService Accounts in namespace ${namespace}: console=True FOR ${sa} IN @{namespace_service_account} Log ${sa.metadata.name} console=True Set Global Variable ${sa_name} ${sa.metadata.name} Set Global Variable ${service_account} ${sa} END List all service accounts for matching name pattern in namespace with label [Arguments] ${name_pattern} ${namespace} ${label} @{namespace_service_account}= List Namespaced Service Account By Pattern ${name_pattern} ${namespace} ${label} Log \nService Accounts in namespace ${namespace}: console=True FOR ${sa} IN @{namespace_service_account} Log ${sa.metadata.name} console=True Dictionary Should Contain Item ${sa.metadata.labels} TestLabel mytestlabel ... msg=Expected labels do not match. END Edit obtained service account [Arguments] ${service_account_name} ${service_account.metadata.name}= Set Variable ${service_account_name} ${service_account.metadata.resource_version}= Set Variable ${None} Set Global Variable ${new_service_account} ${service_account} Create new service account in namespace [Arguments] ${namespace} ${new_sa}= create_namespaced_service_account ${namespace} ${new_service_account} Log ${new_sa} Delete created service account in namespace [Arguments] ${service_account_name} ${namespace} ${status}= delete_namespaced_service_account ${service_account_name} ${namespace} Log ${status} ================================================ FILE: testcases/sts/sts.robot ================================================ *** Settings *** Resource ./sts_kw.robot *** Test Cases *** Statefulset test case example [Tags] statefulset other List all statefulsets in namespace default ================================================ FILE: testcases/sts/sts_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../../src/KubeLibrary/KubeLibrary.py ~/.kube/k3d *** Keywords *** List all statefulsets in namespace [Arguments] ${namespace} ${label}=${EMPTY} @{namespace_statefulsets}= list_namespaced_stateful_set_by_pattern .* ${namespace} ${label} Log \nStatefulsets in namespace ${namespace}: console=True FOR ${statefulset} IN @{namespace_statefulsets} Log ${statefulset.metadata.name} console=True END ================================================ FILE: testcases/system_smoke.robot ================================================ *** Settings *** Resource ./system_smoke_kw.robot *** Variables *** ${KUBELET_VERSION} %{KUBELET_VERSION} ${NUM_NODES} 2 ${NUM_WORKERS} 1 *** Test Cases *** Kubernetes has correct version [Documentation] Test if Kubernetes has correct version [Tags] cluster Given kubernetes has "${NUM_NODES}" healthy nodes When getting kubelet version Then Kubernetes version is correct Pods in kube-system are ok [Documentation] Test if all pods in kube-system initiated correctly and are running or succeeded [Tags] cluster smoke Given kubernetes API responds When getting all pods names in "kube-system" Then all pods in "kube-system" are running or succeeded Traefik has enough replicas [Documentation] Test if Ingress (Traefik) has enough replicas [Tags] cluster smoke Given kubernetes API responds When getting all pods names in "kube-system" Then "traefik*" has "${NUM_WORKERS}" replicas Grafana is persistent [Documentation] Test if Grafana is deployed with persistent storage [Tags] grafana Given kubernetes API responds When getting pvcs in "default" Then "grafana" has "1" pvcs Grafana has 1GB storage [Documentation] Test if Grafana has 1GB storage [Tags] grafana Given kubernetes API responds When getting pvc size for "grafana" in "default" Then pvc size is "1Gi" Grafana service points to pods [Documentation] Test if Grafana service selectors points to pods [Tags] grafana Given kubernetes API responds When getting "grafana" endpoint in "default" Then endpoint points to "grafana*" pod Grafana service is correctly exposed [Documentation] Test if Grafana service is correctly exposed [Tags] grafana Given kubernetes API responds When getting service "grafana" details in "default" Then service is exposed on "3000" port And service has "LoadBalancer" type And service has LB ip assigned Grafana is responding [Documentation] Test if Grafana service ip is accessible [Tags] grafana Given "grafana" service ip and port in "default" is known When session is created Then service is responding on path "/" ================================================ FILE: testcases/system_smoke_kw.robot ================================================ *** Settings *** Library Collections Library RequestsLibrary # For regular execution Library KubeLibrary # For incluster execution #Library KubeLibrary None True False # For development #Library ../src/KubeLibrary/KubeLibrary.py *** Keywords *** kubernetes API responds [Documentation] Check if API response code is 200 ${ping}= k8s_api_ping Should Be Equal As integers ${ping}[1] 200 kubernetes has "${number}" healthy nodes ${node_count}= get_healthy_nodes_count Should Be Equal As integers ${node_count} ${number} getting all pods names in "${namespace}" @{namespace_pods}= list_namespaced_pod_by_pattern .* ${namespace} @{namespace_pods_names}= Filter Names ${namespace_pods} Log ${namespace_pods_names} Set Test Variable ${namespace_pods_names} all pods in "${namespace}" are running or succeeded FOR ${name} IN @{namespace_pods_names} ${status}= read_namespaced_pod_status ${name} ${namespace} Should Be True '${status.phase}'=='Running' or '${status.phase}'=='Succeeded' END accessing "${pattern}" excluding "${exclude}" container images version in "${namespace}" @{pods_images}= get_pods_images_in_namespace ${pattern} ${namespace} ${exclude} Log ${pods_images} Set Test Variable ${pods_images} "${version}" version is used FOR ${Item} IN @{pods_images} Should Be Equal As Strings ${Item} ${version} END getting kubelet version @{node_kubelet_versions}= get_kubelet_version Log ${node_kubelet_versions} Set Test Variable ${node_kubelet_versions} Kubernetes version is correct FOR ${Item} IN @{node_kubelet_versions} Should Contain ${Item} ${KUBELET_VERSION} END "${service}" has "${number}" replicas ${count}= Get Match Count ${namespace_pods_names} ${service} Should Be True ${number} == ${count} "${service}" has at least "${number}" replicas ${count}= Get Match Count ${namespace_pods_names} ${service} Should Be True ${count} >= ${number} getting pvcs in "${namespace}" @{namespace_pvcs}= list_namespaced_persistent_volume_claim ${namespace} @{namespace_pvc_names}= Filter Names ${namespace_pvcs} Log ${namespace_pvc_names} Set Test Variable ${namespace_pvc_names} "${service}" has "${number}" pvcs ${count}= Get Match Count ${namespace_pvc_names} ${service} Should Be True ${number} == ${count} getting pvc size for "${volume}" in "${namespace}" ${pvc}= read_namespaced_persistent_volume_claim ${volume} ${namespace} Log ${pvc} ${pvc_size}= Set Variable ${pvc.status.capacity["storage"]} Set Test Variable ${pvc_size} pvc size is "${size}" Should Be Equal As Strings ${size} ${pvc_size} getting services in "${namespace}" @{namespace_services}= list_namespaced_service ${namespace} Log ${namespace_services} Set Test Variable ${namespace_services} getting service "${service}" details in "${namespace}" ${service_details}= read_namespaced_service ${service} ${namespace} Set Test Variable ${service_details} service is exposed on "${port}" port Should Be Equal As integers ${service_details.spec.ports[0].port} ${port} service has "${type}" type Should Be Equal As Strings ${service_details.spec.type} ${type} service has LB ip assigned Should Not Be Equal As Strings ${service_details.status.load_balancer.ingress[0].ip} None getting "${endpoint}" endpoint in "${namespace}" ${endpoint_details}= read_namespaced_endpoints ${endpoint} ${namespace} Set Test Variable ${endpoint_details} endpoint points to "${pod}" pod Should Match ${endpoint_details.subsets[0].addresses[0].target_ref.name} ${pod} "${service}" service ip and port in "${namespace}" is known ${service_details}= read_namespaced_service ${service} ${namespace} ${service_ip}= Set Variable ${service_details.status.load_balancer.ingress[0].ip} ${service_port}= Set Variable ${service_details.spec.ports[0].port} Set Test Variable ${service_ip} Set Test Variable ${service_port} session is created Create Session service_session http://${service_ip}:${service_port} service is responding on path "${path}" ${resp}= Get Request service_session ${path} Should Be Equal As Strings ${resp.status_code} 200 ================================================ FILE: testcases/test_version.robot ================================================ *** Settings *** Library KubeLibrary *** Test Cases *** Kubernetes Cluster Version Test [Tags] prerelease Get Kubernetes Cluster Version *** Keywords *** Get Kubernetes Cluster Version ${version} K8s Version Log \nk8s version output: console=True FOR ${key} ${value} IN &{version} Log ${key}: ${value} console=True END Log Just the k8s cluster version: ${version['gitVersion']} console=True