diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5d9ee5..6d02608b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ### Unreleased +# v13.14.1 +- Add annotations and labels options to resources defined in kubetools.yaml file + # v13.14.0 - Fix docker-compose conflict when kubetools commands are called without activating their venv - Add Python 3.12 to supported versions, albeit without Flake8 because of CPython bug diff --git a/README.md b/README.md index 754d398a..db74c161 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ tests: deployments: my-app-webserver: + annotations: + imageregistry: "https://hub.docker.com/" + labels: + app.kubernetes.io/name: my-app-webserver serviceAccountName: webserver secrets: secret-volume: diff --git a/kubetools/kubernetes/config/__init__.py b/kubetools/kubernetes/config/__init__.py index d3fc60eb..4e57e487 100644 --- a/kubetools/kubernetes/config/__init__.py +++ b/kubetools/kubernetes/config/__init__.py @@ -166,10 +166,14 @@ def generate_kubernetes_configs_for_project( for name, dependency in config.get('dependencies', {}).items(): dependency_name = make_deployment_name(project_name, name) - dependency_labels = copy_and_update(base_labels, { - ROLE_LABEL_KEY: 'dependency', - NAME_LABEL_KEY: dependency_name, - }) + dependency_labels = copy_and_update( + base_labels, + { + ROLE_LABEL_KEY: 'dependency', + NAME_LABEL_KEY: dependency_name, + }, + dependency.get('labels', {}), + ) node_selector_labels = dependency.get('nodeSelector', None) service_account_name = dependency.get('serviceAccountName', None) @@ -181,7 +185,10 @@ def generate_kubernetes_configs_for_project( deployment_name=name, default_registry=default_registry, ) - app_annotations = copy_and_update(base_annotations) + app_annotations = copy_and_update( + base_annotations, + dependency.get('annotations', {}), + ) if container_ports: services.append(make_service_config( @@ -207,10 +214,14 @@ def generate_kubernetes_configs_for_project( for name, deployment in config.get('deployments', {}).items(): deployment_name = make_deployment_name(project_name, name) - deployment_labels = copy_and_update(base_labels, { - ROLE_LABEL_KEY: 'app', - NAME_LABEL_KEY: deployment_name, - }) + deployment_labels = copy_and_update( + base_labels, + { + ROLE_LABEL_KEY: 'app', + NAME_LABEL_KEY: deployment_name, + }, + deployment.get('labels', {}), + ) node_selector_labels = deployment.get('nodeSelector', None) service_account_name = deployment.get('serviceAccountName', None) @@ -225,6 +236,7 @@ def generate_kubernetes_configs_for_project( app_annotations = copy_and_update( base_annotations, per_deployment_annotations.get(name), + deployment.get('annotations', {}), ) if container_ports: @@ -294,12 +306,16 @@ def generate_kubernetes_configs_for_project( node_selector_labels = job_spec.get('nodeSelector', None) service_account_name = job_spec.get('serviceAccountName', None) secrets = job_spec.get('secrets', None) + app_annotations = copy_and_update( + base_annotations, + job_spec.get('annotations', {}), + ) jobs.append(make_job_config( job_spec, app_name=project_name, labels=job_labels, - annotations=base_annotations, + annotations=app_annotations, envvars=job_envvars, node_selector_labels=node_selector_labels, service_account_name=service_account_name, @@ -309,10 +325,14 @@ def generate_kubernetes_configs_for_project( cronjobs = [] for name, cronjob in config.get('cronjobs', {}).items(): - cronjob_labels = copy_and_update(base_labels, { - ROLE_LABEL_KEY: 'cronjob', - NAME_LABEL_KEY: name, - }) + cronjob_labels = copy_and_update( + base_labels, + { + ROLE_LABEL_KEY: 'cronjob', + NAME_LABEL_KEY: name, + }, + cronjob.get('labels', {}), + ) node_selector_labels = cronjob.get('nodeSelector', None) service_account_name = cronjob.get('serviceAccountName', None) @@ -325,7 +345,11 @@ def generate_kubernetes_configs_for_project( default_registry=default_registry, ) - app_annotations = copy_and_update(base_annotations) + app_annotations = copy_and_update( + base_annotations, + cronjob.get('annotations', {}), + ) + schedule = cronjob['schedule'] concurrency_policy = cronjob['concurrency_policy'] batch_api_version = cronjob.get('batch-api-version') # May depend on target cluster diff --git a/tests/configs/basic_app/k8s_cronjobs.yml b/tests/configs/basic_app/k8s_cronjobs.yml index cf67cdfd..edd640c7 100644 --- a/tests/configs/basic_app/k8s_cronjobs.yml +++ b/tests/configs/basic_app/k8s_cronjobs.yml @@ -38,3 +38,93 @@ spec: imagePullPolicy: 'Always' name: generic-container restartPolicy: OnFailure + +--- + +kind: CronJob +metadata: + name: generic-cronjob-with-annotations + labels: { + kubetools/name: generic-cronjob-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: cronjob + } + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/, + description: 'Run: [''generic-command'']' + } +spec: + schedule: "*/1 * * * *" + startingDeadlineSeconds: 10 + concurrencyPolicy: "Allow" + jobTemplate: + spec: + template: + metadata: + name: generic-cronjob-with-annotations + labels: { + kubetools/name: generic-cronjob-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: cronjob, + } + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/, + description: 'Run: [''generic-command'']' + } + spec: + containers: + - command: [generic-command] + containerContext: generic-context + env: + - {name: KUBE, value: 'true'} + image: generic-image + imagePullPolicy: 'Always' + name: generic-container + restartPolicy: OnFailure + +--- + +kind: CronJob +metadata: + name: generic-cronjob-with-labels + labels: { + app.kubernetes.io/name: generic-cronjob-with-labels, + kubetools/name: generic-cronjob-with-labels, + kubetools/project_name: generic-app, + kubetools/role: cronjob + } + annotations: { + app.kubernetes.io/managed-by: kubetools, + description: 'Run: [''generic-command'']' + } +spec: + schedule: "*/1 * * * *" + startingDeadlineSeconds: 10 + concurrencyPolicy: "Allow" + jobTemplate: + spec: + template: + metadata: + name: generic-cronjob-with-labels + labels: { + app.kubernetes.io/name: generic-cronjob-with-labels, + kubetools/name: generic-cronjob-with-labels, + kubetools/project_name: generic-app, + kubetools/role: cronjob, + } + annotations: { + app.kubernetes.io/managed-by: kubetools, + description: 'Run: [''generic-command'']' + } + spec: + containers: + - command: [generic-command] + containerContext: generic-context + env: + - {name: KUBE, value: 'true'} + image: generic-image + imagePullPolicy: 'Always' + name: generic-container + restartPolicy: OnFailure diff --git a/tests/configs/basic_app/k8s_deployments.yml b/tests/configs/basic_app/k8s_deployments.yml index ea559179..d44a534f 100644 --- a/tests/configs/basic_app/k8s_deployments.yml +++ b/tests/configs/basic_app/k8s_deployments.yml @@ -28,3 +28,97 @@ spec: readinessProbe: httpGet: {path: /ping, port: 80} timeoutSeconds: 5 + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/ + } + labels: { + kubetools/name: generic-app-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: app + } + name: generic-app-with-annotations +spec: + replicas: 1 + revisionHistoryLimit: 5 + selector: + matchLabels: { + kubetools/name: generic-app-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: app + } + template: + metadata: + labels: { + kubetools/name: generic-app-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: app + } + spec: + containers: + - command: [generic-command] + containerContext: generic-context + env: + - {name: KUBE, value: 'true'} + image: generic-image + imagePullPolicy: Always + livenessProbe: + httpGet: {path: /ping, port: 80} + timeoutSeconds: 5 + name: webserver + readinessProbe: + httpGet: {path: /ping, port: 80} + timeoutSeconds: 5 + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: {app.kubernetes.io/managed-by: kubetools} + labels: { + app.kubernetes.io/name: generic-app-with-labels, + kubetools/name: generic-app-with-labels, + kubetools/project_name: generic-app, + kubetools/role: app + } + name: generic-app-with-labels +spec: + replicas: 1 + revisionHistoryLimit: 5 + selector: + matchLabels: { + app.kubernetes.io/name: generic-app-with-labels, + kubetools/name: generic-app-with-labels, + kubetools/project_name: generic-app, + kubetools/role: app + } + template: + metadata: + labels: { + app.kubernetes.io/name: generic-app-with-labels, + kubetools/name: generic-app-with-labels, + kubetools/project_name: generic-app, + kubetools/role: app + } + spec: + containers: + - command: [generic-command] + containerContext: generic-context + env: + - {name: KUBE, value: 'true'} + image: generic-image + imagePullPolicy: Always + livenessProbe: + httpGet: {path: /ping, port: 80} + timeoutSeconds: 5 + name: webserver + readinessProbe: + httpGet: {path: /ping, port: 80} + timeoutSeconds: 5 diff --git a/tests/configs/basic_app/k8s_services.yml b/tests/configs/basic_app/k8s_services.yml index 14a2e9aa..e0acc0d4 100644 --- a/tests/configs/basic_app/k8s_services.yml +++ b/tests/configs/basic_app/k8s_services.yml @@ -9,3 +9,54 @@ spec: - {port: 80, targetPort: 80} selector: {kubetools/name: generic-app, kubetools/project_name: generic-app, kubetools/role: app} type: NodePort + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/ + } + labels: { + kubetools/name: generic-app-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: app + } + name: generic-app-with-annotations +spec: + ports: + - {port: 80, targetPort: 80} + selector: { + kubetools/name: generic-app-with-annotations, + kubetools/project_name: generic-app, + kubetools/role: app + } + type: NodePort + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: { + app.kubernetes.io/managed-by: kubetools + } + labels: { + app.kubernetes.io/name: generic-app-with-labels, + kubetools/name: generic-app-with-labels, + kubetools/project_name: generic-app, + kubetools/role: app + } + name: generic-app-with-labels +spec: + ports: + - {port: 80, targetPort: 80} + selector: { + app.kubernetes.io/name: generic-app-with-labels, + kubetools/name: generic-app-with-labels, + kubetools/project_name: generic-app, + kubetools/role: app + } + type: NodePort diff --git a/tests/configs/basic_app/kubetools.yml b/tests/configs/basic_app/kubetools.yml index 894c23c8..6fd186de 100644 --- a/tests/configs/basic_app/kubetools.yml +++ b/tests/configs/basic_app/kubetools.yml @@ -27,6 +27,28 @@ deployments: timeoutSeconds: 5 httpGet: path: /ping + generic-app-with-annotations: + annotations: + imageregistry: "https://hub.docker.com/" + containers: + webserver: + command: [uwsgi, --ini, /etc/uwsgi.conf] + containerContext: generic-context + probes: + timeoutSeconds: 5 + httpGet: + path: /ping + generic-app-with-labels: + labels: + app.kubernetes.io/name: generic-app-with-labels + containers: + webserver: + command: [uwsgi, --ini, /etc/uwsgi.conf] + containerContext: generic-context + probes: + timeoutSeconds: 5 + httpGet: + path: /ping cronjobs: @@ -36,3 +58,19 @@ cronjobs: containers: generic-container: containerContext: generic-context + generic-cronjob-with-annotations: + annotations: + imageregistry: "https://hub.docker.com/" + schedule: "*/1 * * * *" + concurrency_policy: "Allow" + containers: + generic-container: + containerContext: generic-context + generic-cronjob-with-labels: + labels: + app.kubernetes.io/name: generic-cronjob-with-labels + schedule: "*/1 * * * *" + concurrency_policy: "Allow" + containers: + generic-container: + containerContext: generic-context diff --git a/tests/configs/dependencies/k8s_deployments.yml b/tests/configs/dependencies/k8s_deployments.yml index 429ebbfc..66e8f550 100644 --- a/tests/configs/dependencies/k8s_deployments.yml +++ b/tests/configs/dependencies/k8s_deployments.yml @@ -53,3 +53,76 @@ spec: image: memcache:1.4.33 imagePullPolicy: Always name: memcache-2 + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/ + } + labels: {kubetools/name: dependencies-memcache-with-annotations, kubetools/project_name: dependencies, + kubetools/role: dependency} + name: dependencies-memcache-with-annotations +spec: + replicas: 1 + revisionHistoryLimit: 5 + selector: + matchLabels: {kubetools/name: dependencies-memcache-with-annotations, kubetools/project_name: dependencies, + kubetools/role: dependency} + template: + metadata: + labels: {kubetools/name: dependencies-memcache-with-annotations, kubetools/project_name: dependencies, + kubetools/role: dependency} + spec: + containers: + - command: [memcached, -u, root, -I, 10m] + containerContext: memcache + env: + - {name: KUBE, value: 'true'} + image: memcache:1.4.33 + imagePullPolicy: Always + name: memcache-with-annotations + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: {app.kubernetes.io/managed-by: kubetools} + labels: { + app.kubernetes.io/name: memcache-with-labels, + kubetools/name: dependencies-memcache-with-labels, + kubetools/project_name: dependencies, + kubetools/role: dependency + } + name: dependencies-memcache-with-labels +spec: + replicas: 1 + revisionHistoryLimit: 5 + selector: + matchLabels: { + app.kubernetes.io/name: memcache-with-labels, + kubetools/name: dependencies-memcache-with-labels, + kubetools/project_name: dependencies, + kubetools/role: dependency + } + template: + metadata: + labels: { + app.kubernetes.io/name: memcache-with-labels, + kubetools/name: dependencies-memcache-with-labels, + kubetools/project_name: dependencies, + kubetools/role: dependency + } + spec: + containers: + - command: [memcached, -u, root, -I, 10m] + containerContext: memcache + env: + - {name: KUBE, value: 'true'} + image: memcache:1.4.33 + imagePullPolicy: Always + name: memcache-with-labels diff --git a/tests/configs/dependencies/k8s_services.yml b/tests/configs/dependencies/k8s_services.yml index 1d822204..dfc357ce 100644 --- a/tests/configs/dependencies/k8s_services.yml +++ b/tests/configs/dependencies/k8s_services.yml @@ -27,3 +27,46 @@ spec: selector: {kubetools/name: dependencies-memcache-2, kubetools/project_name: dependencies, kubetools/role: dependency} type: NodePort + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: { + app.kubernetes.io/managed-by: kubetools, + imageregistry: https://hub.docker.com/ + } + labels: {kubetools/name: dependencies-memcache-with-annotations, kubetools/project_name: dependencies, + kubetools/role: dependency} + name: dependencies-memcache-with-annotations +spec: + ports: + - {port: 11211, targetPort: 11211} + selector: {kubetools/name: dependencies-memcache-with-annotations, kubetools/project_name: dependencies, + kubetools/role: dependency} + type: NodePort + +--- + +apiVersion: v1 +kind: Service +metadata: + annotations: {app.kubernetes.io/managed-by: kubetools} + labels: { + app.kubernetes.io/name: memcache-with-labels, + kubetools/name: dependencies-memcache-with-labels, + kubetools/project_name: dependencies, + kubetools/role: dependency + } + name: dependencies-memcache-with-labels +spec: + ports: + - {port: 11211, targetPort: 11211} + selector: { + app.kubernetes.io/name: memcache-with-labels, + kubetools/name: dependencies-memcache-with-labels, + kubetools/project_name: dependencies, + kubetools/role: dependency + } + type: NodePort diff --git a/tests/configs/dependencies/kubetools.yml b/tests/configs/dependencies/kubetools.yml index a3d70d87..04cb320d 100644 --- a/tests/configs/dependencies/kubetools.yml +++ b/tests/configs/dependencies/kubetools.yml @@ -20,6 +20,19 @@ dependencies: memcache-2: containerContext: memcache + memcache-with-annotations: + annotations: + imageregistry: https://hub.docker.com/ + containers: + memcache-with-annotations: + containerContext: memcache + memcache-with-labels: + labels: + app.kubernetes.io/name: memcache-with-labels + containers: + memcache-with-labels: + containerContext: memcache + elasticsearch: conditions: dev: true