Source code for saltext.kubernetes.states.kubernetes

"""
Manage kubernetes resources as salt states
==========================================

NOTE: This module requires the proper pillar values set. See
salt.modules.kubernetesmod for more information.

.. warning::

    Configuration options will change in 2019.2.0.

The kubernetes module is used to manage different kubernetes resources.


.. code-block:: yaml

    my-nginx:
      kubernetes.deployment_present:
        - namespace: default
          metadata:
            app: frontend
          spec:
            replicas: 1
            template:
              metadata:
                labels:
                  run: my-nginx
              spec:
                containers:
                - name: my-nginx
                  image: nginx
                  ports:
                  - containerPort: 80

    my-mariadb:
      kubernetes.deployment_absent:
        - namespace: default

    # kubernetes deployment as specified inside of
    # a file containing the definition of the the
    # deployment using the official kubernetes format
    redis-master-deployment:
      kubernetes.deployment_present:
        - name: redis-master
        - source: salt://k8s/redis-master-deployment.yml
      require:
        - pip: kubernetes-python-module

    # kubernetes service as specified inside of
    # a file containing the definition of the the
    # service using the official kubernetes format
    redis-master-service:
      kubernetes.service_present:
        - name: redis-master
        - source: salt://k8s/redis-master-service.yml
      require:
        - kubernetes.deployment_present: redis-master

    # kubernetes deployment as specified inside of
    # a file containing the definition of the the
    # deployment using the official kubernetes format
    # plus some jinja directives
     nginx-source-template:
      kubernetes.deployment_present:
        - source: salt://k8s/nginx.yml.jinja
        - template: jinja
      require:
        - pip: kubernetes-python-module

    # kubernetes deployment using a template with custom template_context variables
    nginx-template-with-template_context:
      kubernetes.deployment_present:
        - name: nginx-template
        - source: salt://k8s/nginx-template.yml.jinja
        - template: jinja
        - template_context:
            replicas: 3
            nginx_version: 1.19
            environment: production
            app_label: frontend

    # kubernetes secret with template_context variables
    cert-secret-with-template_context:
      kubernetes.secret_present:
        - name: tls-cert
        - source: salt://k8s/tls-cert.yml.jinja
        - template: jinja
        - template_context:
            cert_name: myapp.example.com
            cert_data: |
                -----BEGIN CERTIFICATE-----
                ...
                -----END CERTIFICATE-----
        - secret_type: kubernetes.io/tls

    # Kubernetes secret
    k8s-secret:
      kubernetes.secret_present:
        - name: top-secret
          data:
            key1: value1
            key2: value2
            key3: value3

.. versionadded:: 2017.7.0
"""

import copy
import logging

from salt.exceptions import CommandExecutionError
from salt.utils.dictdiffer import RecursiveDictDiffer

log = logging.getLogger(__name__)

__virtualname__ = "kubernetes"


[docs] def __virtual__(): """ Only load if the kubernetes module is available in __salt__ """ if "kubernetes.ping" in __salt__: return True return (False, "kubernetes module could not be loaded")
def _error(ret, err_msg): """ Helper function to propagate errors to the end user. """ ret["result"] = False ret["comment"] = err_msg return ret def _changes(old, new): """ Return a changes dict using RecursiveDictDiffer for concise reporting. """ try: # Salt 3007 and earlier: list_dict_matchers does not exist. diff = RecursiveDictDiffer( # pylint: disable=no-value-for-parameter old, new, ignore_missing_keys=False ) except TypeError: # Salt 3008+: list_dict_matchers is required. diff = RecursiveDictDiffer(old, new, False, []) # pylint: disable=too-many-function-args return {"old": diff.old_values, "new": diff.new_values}
[docs] def deployment_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensures that the named deployment is absent from the given namespace. name The name of the deployment namespace The name of the namespace wait .. versionadded:: 2.0.0 If set to True, the function will wait until the deployment is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the deployment to Example: .. code-block:: yaml my-nginx: kubernetes.deployment_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: deployment = __salt__["kubernetes.show_deployment"](name, namespace, **kwargs) if deployment is None: ret["result"] = True ret["comment"] = "The deployment does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "The deployment is going to be deleted" ret["changes"] = { "old": "present", "new": "absent", } return ret __salt__["kubernetes.delete_deployment"]( name, namespace, wait=wait, timeout=timeout, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"Deployment {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def deployment_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ Ensures that the named deployment is present inside of the specified namespace with the given metadata and spec. If the deployment exists, it will be patched with the desired state. name The name of the deployment. namespace The namespace holding the deployment. The 'default' one is going to be used unless a different one is specified. metadata The metadata of the deployment object. spec The spec of the deployment object. source A file containing the definition of the deployment (metadata and spec) in the official kubernetes format. template Template engine to be used to render the source file. template_context .. versionadded:: 2.0.0 Variables to be passed into the template. wait .. versionadded:: 2.0.0 If set to True, the function will wait until the deployment is ready. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the deployment to be ready. Example: .. code-block:: yaml my-nginx: kubernetes.deployment_present: - namespace: default - metadata: app: frontend - spec: replicas: 1 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: deployment = __salt__["kubernetes.show_deployment"](name, namespace, **kwargs) if deployment is None: try: res = __salt__["kubernetes.create_deployment"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The deployment is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The deployment is going to be created" else: ret["result"] = True ret["comment"] = "Deployment created" return ret # Deployment exists — build the patch object if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_deployment"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The deployment is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == deployment: ret["result"] = True ret["comment"] = "The deployment is already in the desired state" return ret ret["changes"] = _changes(deployment, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The deployment is going to be updated" else: ret["result"] = True ret["comment"] = "Deployment updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def statefulset_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ .. versionadded:: 2.1.0 Ensures that the named statefulset is absent from the given namespace. name The name of the statefulset namespace The name of the namespace wait If set to True, the function will wait until the statefulset is deleted. timeout The time in seconds to wait for the statefulset to be deleted. Example: .. code-block:: yaml my-statefulset: kubernetes.statefulset_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: statefulset = __salt__["kubernetes.show_statefulset"](name, namespace, **kwargs) if statefulset is None: ret["result"] = True ret["comment"] = "The statefulset does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "The statefulset is going to be deleted" ret["changes"] = { "old": "present", "new": "absent", } return ret __salt__["kubernetes.delete_statefulset"]( name, namespace, wait=wait, timeout=timeout, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"StatefulSet {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def statefulset_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ .. versionadded:: 2.1.0 Ensures that the named statefulset is present inside of the specified namespace with the given metadata and spec. If the statefulset exists, it will be patched with the desired state. name The name of the statefulset. namespace The namespace holding the statefulset. The 'default' one is going to be used unless a different one is specified. metadata The metadata of the statefulset object. spec The spec of the statefulset object. source A file containing the definition of the statefulset (metadata and spec) in the official kubernetes format. template Template engine to be used to render the source file. template_context Variables to be passed into the template. wait If set to True, the function will wait until the statefulset is ready. timeout The time in seconds to wait for the statefulset to be ready. Example: .. code-block:: yaml my-statefulset: kubernetes.statefulset_present: - namespace: default - metadata: app: myapp - spec: serviceName: my-service replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: myapp:latest ports: - containerPort: 8080 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: statefulset = __salt__["kubernetes.show_statefulset"](name, namespace, **kwargs) if statefulset is None: try: res = __salt__["kubernetes.create_statefulset"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The statefulset is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The statefulset is going to be created" else: ret["result"] = True ret["comment"] = "StatefulSet created" return ret # StatefulSet exists — build the patch object if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_statefulset"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The statefulset is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == statefulset: ret["result"] = True ret["comment"] = "The statefulset is already in the desired state" return ret ret["changes"] = _changes(statefulset, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The statefulset is going to be updated" else: ret["result"] = True ret["comment"] = "StatefulSet updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def replicaset_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ .. versionadded:: 2.1.0 Ensures that the named replicaset is absent from the given namespace. name The name of the replicaset namespace The namespace of the replicaset wait Wait for replicaset to be deleted (default: False) timeout Timeout in seconds to wait for replicaset deletion (default: 60) CLI Example: .. code-block:: yaml my-replicaset: kubernetes.replicaset_absent: namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: replicaset = __salt__["kubernetes.show_replicaset"](name, namespace, **kwargs) if replicaset is None: ret["result"] = True ret["comment"] = "The replicaset does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "The replicaset is going to be deleted" ret["changes"] = { "old": "present", "new": "absent", } return ret __salt__["kubernetes.delete_replicaset"]( name, namespace, wait=wait, timeout=timeout, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"ReplicaSet {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def replicaset_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ .. versionadded:: 2.1.0 Ensures that the named replicaset is present inside of the specified namespace with the given metadata and spec. If the replicaset exists, it will be patched with the desired state. name The name of the replicaset namespace The namespace of the replicaset metadata A dictionary representing the metadata of the replicaset spec A dictionary representing the spec of the replicaset source File path to replicaset definition template Template engine to use to render the source file saltenv Salt environment to pull the source file from template_context Variables to make available in templated files wait Wait for replicaset to become ready (default: False) timeout Timeout in seconds to wait for replicaset (default: 60) CLI Example: .. code-block:: yaml my-replicaset: kubernetes.replicaset_present: namespace: default metadata: labels: app: my-app spec: replicas: 3 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: replicaset = __salt__["kubernetes.show_replicaset"](name, namespace, **kwargs) if replicaset is None: try: res = __salt__["kubernetes.create_replicaset"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The replicaset is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The replicaset is going to be created" else: ret["result"] = True ret["comment"] = "ReplicaSet created" return ret if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_replicaset"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The replicaset is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == replicaset: ret["result"] = True ret["comment"] = "The replicaset is already in the desired state" return ret ret["changes"] = _changes(replicaset, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The replicaset is going to be updated" else: ret["result"] = True ret["comment"] = "ReplicaSet updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def daemonset_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ .. versionadded:: 2.1.0 Ensures that the named daemonset is absent from the given namespace. name The name of the daemonset namespace The namespace of the daemonset wait Wait for daemonset to be deleted (default: False) timeout Timeout in seconds to wait for daemonset deletion (default: 60) CLI Example: .. code-block:: yaml my-daemonset: kubernetes.daemonset_absent: namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: daemonset = __salt__["kubernetes.show_daemonset"](name, namespace, **kwargs) if daemonset is None: ret["result"] = True ret["comment"] = "The daemonset does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "The daemonset is going to be deleted" ret["changes"] = { "old": "present", "new": "absent", } return ret __salt__["kubernetes.delete_daemonset"]( name, namespace, wait=wait, timeout=timeout, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"DaemonSet {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def daemonset_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ .. versionadded:: 2.1.0 Ensures that the named daemonset is present inside of the specified namespace with the given metadata and spec. If the daemonset exists, it will be patched with the desired state. name The name of the daemonset namespace The namespace of the daemonset metadata Metadata for the daemonset spec Specification for the daemonset source File path to daemonset definition template Template engine to use to render the source file saltenv Salt environment to pull the source file from template_context Variables to make available in templated files wait Wait for daemonset to become ready (default: False) timeout Timeout in seconds to wait for daemonset (default: 60) CLI Example: .. code-block:: yaml my-daemonset: kubernetes.daemonset_present: namespace: default metadata: labels: app: my-daemonset spec: replicas: 3 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: daemonset = __salt__["kubernetes.show_daemonset"](name, namespace, **kwargs) if daemonset is None: try: res = __salt__["kubernetes.create_daemonset"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The daemonset is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The daemonset is going to be created" else: ret["result"] = True ret["comment"] = "DaemonSet created" return ret if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_daemonset"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The daemonset is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == daemonset: ret["result"] = True ret["comment"] = "The daemonset is already in the desired state" return ret ret["changes"] = _changes(daemonset, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The daemonset is going to be updated" else: ret["result"] = True ret["comment"] = "DaemonSet updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def storageclass_absent(name, wait=False, timeout=60, **kwargs): """ .. versionadded:: 2.1.0 Ensures that the named storageclass is absent. name The name of the storageclass wait Wait for storageclass to be deleted (default: False) timeout Timeout in seconds to wait for storageclass deletion (default: 60) CLI Example: .. code-block:: yaml my-storageclass: kubernetes.storageclass_absent: """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: storageclass = __salt__["kubernetes.show_storageclass"](name, **kwargs) if storageclass is None: ret["result"] = True ret["comment"] = "The storageclass does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "The storageclass is going to be deleted" ret["changes"] = { "old": "present", "new": "absent", } return ret __salt__["kubernetes.delete_storageclass"](name, wait=wait, timeout=timeout, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"StorageClass {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def storageclass_present( name, metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ .. versionadded:: 2.1.0 Ensures that the named storageclass is present with the given metadata and spec. If the storageclass exists, it will be patched with the desired state. name The name of the storageclass metadata Metadata for the storageclass spec Specification for the storageclass source File path to storageclass definition template Template engine to use to render the source file template_context Variables to make available in templated files wait Wait for storageclass to become ready (default: False) timeout Timeout in seconds to wait for storageclass (default: 60) CLI Example: .. code-block:: yaml my-storageclass: kubernetes.storageclass_present: metadata: labels: app: my-storageclass spec: provisioner: kubernetes.io/no-provisioner """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if not source and metadata is None: metadata = {} if not source and spec is None: spec = {} try: storageclass = __salt__["kubernetes.show_storageclass"](name, **kwargs) if storageclass is None: try: res = __salt__["kubernetes.create_storageclass"]( name=name, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The storageclass is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The storageclass is going to be created" else: ret["result"] = True ret["comment"] = "StorageClass created" return ret if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_storageclass"]( name, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The storageclass is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == storageclass: ret["result"] = True ret["comment"] = "The storageclass is already in the desired state" return ret ret["changes"] = _changes(storageclass, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The storageclass is going to be updated" else: ret["result"] = True ret["comment"] = "StorageClass updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def service_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ Ensures that the named service is present inside of the specified namespace with the given metadata and spec. If the service exists, it will be patched with the desired state. name The name of the service. namespace The namespace holding the service. The 'default' one is going to be used unless a different one is specified. metadata The metadata of the service object. spec The spec of the service object. source A file containing the definition of the service (metadata and spec) in the official kubernetes format. template Template engine to be used to render the source file. template_context .. versionadded:: 2.0.0 Variables to be passed into the template. wait .. versionadded:: 2.0.0 If set to True, the function will wait until the service is created. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the service to be created. Example: .. code-block:: yaml my-service: kubernetes.service_present: - namespace: default - metadata: app: frontend - spec: ports: - port: 80 targetPort: 80 protocol: TCP selector: app: frontend """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: service = __salt__["kubernetes.show_service"](name, namespace, **kwargs) if service is None: try: res = __salt__["kubernetes.create_service"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The service is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The service is going to be created" else: ret["result"] = True ret["comment"] = "Service created" return ret # Service exists — build the patch object if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_service"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The service is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == service: ret["result"] = True ret["comment"] = "The service is already in the desired state" return ret ret["changes"] = _changes(service, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The service is going to be updated" else: ret["result"] = True ret["comment"] = "Service updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def service_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensures that the named service is absent from the given namespace. name The name of the service namespace The name of the namespace wait .. versionadded:: 2.0.0 If set to True, the function will wait until the service is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the service to be deleted. Example: .. code-block:: yaml my_service: kubernetes.service_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: service = __salt__["kubernetes.show_service"](name, namespace, **kwargs) if service is None: ret["result"] = True ret["comment"] = "The service does not exist" return ret if __opts__["test"]: ret["comment"] = "The service is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.delete_service"](name, namespace, wait=wait, timeout=timeout, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"Service {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def namespace_absent(name, wait=False, timeout=60, **kwargs): """ Ensures that the named namespace is absent. name The name of the namespace wait .. versionadded:: 2.0.0 If set to True, the function will wait until the namespace is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the namespace to be deleted. Example: .. code-block:: yaml my_namespace: kubernetes.namespace_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: namespace = __salt__["kubernetes.show_namespace"](name, **kwargs) if namespace is None: ret["result"] = True ret["comment"] = "The namespace does not exist" return ret if __opts__["test"]: ret["comment"] = "The namespace is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.delete_namespace"](name, wait=wait, timeout=timeout, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"Namespace {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def namespace_present(name, **kwargs): """ Ensures that the named namespace is present. name The name of the namespace. Example: .. code-block:: yaml my_namespace: kubernetes.namespace_present: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: namespace = __salt__["kubernetes.show_namespace"](name, **kwargs) if namespace is None: if __opts__["test"]: ret["result"] = None ret["comment"] = "The namespace is going to be created" ret["changes"] = {"old": {}, "new": {"metadata": {"name": name}}} return ret res = __salt__["kubernetes.create_namespace"](name, **kwargs) ret["result"] = True ret["changes"] = {"old": {}, "new": res} else: ret["result"] = True ret["comment"] = "The namespace already exists" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def secret_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensures that the named secret is absent from the given namespace. name The name of the secret namespace The name of the namespace wait .. versionadded:: 2.0.0 If set to True, the function will wait until the secret is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the secret to be deleted. Example: .. code-block:: yaml my_secret: kubernetes.secret_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: secret = __salt__["kubernetes.show_secret"](name, namespace, **kwargs) if secret is None: ret["result"] = True ret["comment"] = "The secret does not exist" return ret if __opts__["test"]: ret["comment"] = "The secret is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.delete_secret"](name, namespace, wait=wait, timeout=timeout, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"Secret {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def secret_present( name, namespace="default", data=None, source=None, template=None, template_context=None, secret_type=None, metadata=None, wait=False, timeout=60, **kwargs, ): """ Ensures that the named secret is present inside of the specified namespace with the given data. If the secret exists, it will be patched with the desired state. name The name of the secret. namespace The namespace holding the secret. The 'default' one is going to be used unless a different one is specified. data The dictionary holding the secrets. source A file containing the data of the secret in plain format. template Template engine to be used to render the source file. template_context .. versionadded:: 2.0.0 Variables to be passed into the template. secret_type .. versionadded:: 2.0.0 The type of secret to create. Defaults to ``Opaque``. metadata .. versionadded:: 2.0.0 The metadata to include in the secret (annotations, labels, etc). wait .. versionadded:: 2.0.0 If set to True, the function will wait until the secret is created. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the secret to be created. Example: .. code-block:: yaml my_secret: kubernetes.secret_present: - namespace: default - data: key1: value1 key2: value2 key3: value3 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if data and source: return _error(ret, "'source' cannot be used in combination with 'data'") if metadata is None: metadata = {} try: secret = __salt__["kubernetes.show_secret"](name, namespace, **kwargs) if secret is None: if data is None: data = {} try: res = __salt__["kubernetes.create_secret"]( name=name, namespace=namespace, data=data, source=source, template=template, saltenv=__env__, template_context=template_context, secret_type=secret_type, metadata=metadata, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The secret is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = { "old": {}, "new": {"data": list(res.get("data") or [])}, } if __opts__["test"]: ret["result"] = None ret["comment"] = "The secret is going to be created" else: ret["result"] = True ret["comment"] = "Secret created" return ret # Secret exists — build the patch object if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if data: patch_obj["data"] = data if secret_type: patch_obj["type"] = secret_type patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_secret"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The secret is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == secret: ret["result"] = True ret["comment"] = "The secret is already in the desired state" return ret ret["changes"] = { "old": {"data": list(secret.get("data") or [])}, "new": {"data": list(res.get("data") or [])}, } if __opts__["test"]: ret["result"] = None ret["comment"] = "The secret is going to be updated" else: ret["result"] = True ret["comment"] = "Secret updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def configmap_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensures that the named configmap is absent from the given namespace. name The name of the configmap namespace The namespace holding the configmap. The 'default' one is going to be used unless a different one is specified. wait .. versionadded:: 2.0.0 If set to True, the function will wait until the configmap is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the configmap to be deleted. Example: .. code-block:: yaml my_configmap: kubernetes.configmap_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: configmap = __salt__["kubernetes.show_configmap"](name, namespace, **kwargs) if configmap is None: ret["result"] = True ret["comment"] = "The configmap does not exist" return ret if __opts__["test"]: ret["comment"] = "The configmap is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.delete_configmap"]( name, namespace, wait=wait, timeout=timeout, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"ConfigMap {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def configmap_present( name, namespace="default", data=None, source=None, template=None, template_context=None, wait=False, timeout=60, **kwargs, ): """ Ensures that the named configmap is present inside of the specified namespace with the given data. If the configmap exists, it will be patched with the desired state. name The name of the configmap. namespace The namespace holding the configmap. The 'default' one is going to be used unless a different one is specified. data The dictionary holding the configmaps. source A file containing the data of the configmap in plain format. .. versionchanged:: 2.0.0 The configmap definition must be a proper spec with the configmap data in the ``data`` key. In previous versions, the rendered output was used as the data directly. template Template engine to be used to render the source file. template_context .. versionadded:: 2.0.0 Variables to be passed into the template. wait .. versionadded:: 2.0.0 If set to True, the function will wait until the configmap is created. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the configmap to be created. Example: .. code-block:: yaml my_configmap: kubernetes.configmap_present: - namespace: default - data: key1: value1 key2: value2 key3: value3 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if data and source: return _error(ret, "'source' cannot be used in combination with 'data'") elif data is None: data = {} try: configmap = __salt__["kubernetes.show_configmap"](name, namespace, **kwargs) if configmap is None: try: res = __salt__["kubernetes.create_configmap"]( name=name, namespace=namespace, data=data, source=source, template=template, saltenv=__env__, template_context=template_context, dry_run=bool(__opts__["test"]), wait=wait if not __opts__["test"] else False, timeout=timeout, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The configmap is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = "The configmap is going to be created" else: ret["result"] = True ret["comment"] = "ConfigMap created" return ret # ConfigMap exists — build the patch object if source: patch_kwargs = { "source": source, "template": template, "template_context": template_context, } else: patch_obj = {} if data: patch_obj["data"] = data patch_kwargs = {"patch": patch_obj} try: res = __salt__["kubernetes.patch_configmap"]( name, namespace, dry_run=bool(__opts__["test"]), wait=wait, timeout=timeout, **patch_kwargs, **kwargs, ) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( "The configmap is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret if res == configmap: ret["result"] = True ret["comment"] = "The configmap is already in the desired state" return ret ret["changes"] = _changes(configmap, res) if __opts__["test"]: ret["result"] = None ret["comment"] = "The configmap is going to be updated" else: ret["result"] = True ret["comment"] = "ConfigMap updated" except CommandExecutionError as err: ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def pod_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensures that the named pod is absent from the given namespace. name The name of the pod namespace The name of the namespace wait .. versionadded:: 2.0.0 If set to True, the function will wait until the pod is deleted. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the pod to be deleted. Example: .. code-block:: yaml my_pod: kubernetes.pod_absent: - namespace: default """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: pod = __salt__["kubernetes.show_pod"](name, namespace, **kwargs) if pod is None: ret["result"] = True ret["comment"] = "The pod does not exist" return ret if __opts__["test"]: ret["comment"] = "The pod is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.delete_pod"](name, namespace, wait=wait, timeout=timeout, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"Pod {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def pod_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, wait=False, timeout=60, **kwargs, ): """ Ensures that the named pod is present inside of the specified namespace with the given metadata and spec. .. note:: Pods are immutable once created. If the pod already exists, this state will report success without changes. To update a pod, first remove it with ``pod_absent`` and then recreate it. For managed workloads, consider using ``deployment_present`` instead. name The name of the pod. namespace The namespace holding the pod. The 'default' one is going to be used unless a different one is specified. metadata The metadata of the pod object. spec The spec of the pod object. source A file containing the definition of the pod (metadata and spec) in the official kubernetes format. template Template engine to be used to render the source file. template_context .. versionadded:: 2.0.0 Variables to be passed into the template. wait .. versionadded:: 2.0.0 If set to True, the function will wait until the pod is created. timeout .. versionadded:: 2.0.0 The time in seconds to wait for the pod to be created. Example: .. code-block:: yaml my_pod: kubernetes.pod_present: - namespace: default - metadata: app: frontend - spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if metadata is None: metadata = {} if spec is None: spec = {} try: pod = __salt__["kubernetes.show_pod"](name, namespace, **kwargs) if pod is None: if __opts__["test"]: ret["result"] = None ret["comment"] = "The pod is going to be created" return ret res = __salt__["kubernetes.create_pod"]( name=name, namespace=namespace, metadata=metadata, spec=spec, source=source, template=template, saltenv=__env__, template_context=template_context, wait=wait, timeout=timeout, **kwargs, ) ret["result"] = True ret["changes"] = {"old": {}, "new": res} ret["comment"] = "Pod created" return ret # Pod already exists — pods are immutable, report as already present ret["result"] = True ret["comment"] = ( "The pod already exists. Pods are immutable once created. " "To update, remove with pod_absent first, then recreate. " "For managed workloads, consider using deployment_present instead." ) except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_label_absent(name, node, **kwargs): """ Ensures that the named label is absent from the node. name The name of the label node The name of the node Example: .. code-block:: yaml my_label: kubernetes.node_label_absent: - node: node_name """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: labels = __salt__["kubernetes.node_labels"](node, **kwargs) if name not in labels: ret["result"] = True ret["comment"] = "The label does not exist" return ret if __opts__["test"]: ret["comment"] = "The label is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.node_remove_label"](node_name=node, label_name=name, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = "Label removed from node" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_label_folder_absent(name, node, **kwargs): """ Ensures the label folder doesn't exist on the specified node. name The name of label folder node The name of the node Example: .. code-block:: yaml my_label_folder: kubernetes.node_label_folder_absent: - node: node_name """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: labels = __salt__["kubernetes.node_labels"](node, **kwargs) folder = name.strip("/") + "/" labels_to_drop = [] new_labels = [] for label in labels: if label.startswith(folder): labels_to_drop.append(label) else: new_labels.append(label) if not labels_to_drop: ret["result"] = True ret["comment"] = "The label folder does not exist" return ret if __opts__["test"]: ret["comment"] = "The label folder is going to be deleted" ret["result"] = None ret["changes"] = {"old": list(labels), "new": new_labels} return ret for label in labels_to_drop: __salt__["kubernetes.node_remove_label"](node_name=node, label_name=label, **kwargs) ret["result"] = True ret["changes"] = {"old": list(labels), "new": new_labels} ret["comment"] = "Label folder removed from node" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_label_present(name, node, value, **kwargs): """ Ensures that the named label is set on the named node with the given value. If the label exists it will be replaced. name The name of the label. value Value of the label. node Node to change. Example: .. code-block:: yaml my_label: kubernetes.node_label_present: - node: node_name - value: my_value """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: labels = __salt__["kubernetes.node_labels"](node, **kwargs) if name not in labels: if __opts__["test"]: ret["result"] = None ret["comment"] = "The label is going to be set" old_labels = copy.copy(labels) new_labels = copy.copy(labels) new_labels[name] = value ret["changes"] = {"old": old_labels, "new": new_labels} return ret __salt__["kubernetes.node_add_label"]( label_name=name, label_value=value, node_name=node, **kwargs ) elif labels[name] == value: ret["result"] = True ret["comment"] = "The label is already set and has the specified value" return ret else: if __opts__["test"]: ret["result"] = None ret["comment"] = "The label is going to be updated" old_labels = copy.copy(labels) new_labels = copy.copy(labels) new_labels[name] = value ret["changes"] = {"old": old_labels, "new": new_labels} return ret ret["comment"] = "The label is already set, changing the value" __salt__["kubernetes.node_add_label"]( node_name=node, label_name=name, label_value=value, **kwargs ) old_labels = copy.copy(labels) labels[name] = value ret["changes"] = {"old": old_labels, "new": labels} ret["result"] = True except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_annotation_absent(name, node, **kwargs): """ Ensure the named annotation is absent from *node*. .. versionadded:: 2.1.0 name The annotation key (e.g. ``example.com/maintenance``). node The name of the node. Example: .. code-block:: yaml clear-maintenance-flag: kubernetes.node_annotation_absent: - name: example.com/maintenance - node: worker-0 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: annotations = __salt__["kubernetes.node_annotations"](node, **kwargs) if name not in annotations: ret["result"] = True ret["comment"] = "The annotation does not exist" return ret if __opts__["test"]: ret["comment"] = "The annotation is going to be deleted" ret["result"] = None ret["changes"] = {"old": "present", "new": "absent"} return ret __salt__["kubernetes.node_remove_annotation"]( node_name=node, annotation_name=name, **kwargs ) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = "Annotation removed from node" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_annotation_folder_absent(name, node, **kwargs): """ Ensure no annotations under the ``name/`` prefix exist on *node*. .. versionadded:: 2.1.0 Useful for cleaning up a whole set of annotations written by a departing controller — ``example.com/`` removes every annotation whose key starts with that prefix. name The annotation prefix (e.g. ``example.com``); the trailing ``/`` is added automatically. node The name of the node. Example: .. code-block:: yaml example.com: kubernetes.node_annotation_folder_absent: - node: worker-0 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: annotations = __salt__["kubernetes.node_annotations"](node, **kwargs) folder = name.strip("/") + "/" to_drop = [] remaining = [] for key in annotations: (to_drop if key.startswith(folder) else remaining).append(key) if not to_drop: ret["result"] = True ret["comment"] = "The annotation folder does not exist" return ret if __opts__["test"]: ret["comment"] = "The annotation folder is going to be deleted" ret["result"] = None ret["changes"] = {"old": list(annotations), "new": remaining} return ret for key in to_drop: __salt__["kubernetes.node_remove_annotation"]( node_name=node, annotation_name=key, **kwargs ) ret["result"] = True ret["changes"] = {"old": list(annotations), "new": remaining} ret["comment"] = "Annotation folder removed from node" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def node_annotation_present(name, node, value, **kwargs): """ Ensure the named annotation is set on *node* with *value*. .. versionadded:: 2.1.0 If the annotation exists with a different value it is replaced. name The annotation key. value The annotation value (always stringified — Kubernetes annotations are string-valued). node The name of the node. Example: .. code-block:: yaml example.com/maintenance: kubernetes.node_annotation_present: - node: worker-0 - value: "2026-05-16" """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: annotations = __salt__["kubernetes.node_annotations"](node, **kwargs) if name not in annotations: if __opts__["test"]: ret["result"] = None ret["comment"] = "The annotation is going to be set" old = copy.copy(annotations) new = copy.copy(annotations) new[name] = value ret["changes"] = {"old": old, "new": new} return ret __salt__["kubernetes.node_add_annotation"]( annotation_name=name, annotation_value=value, node_name=node, **kwargs ) elif annotations[name] == value: ret["result"] = True ret["comment"] = "The annotation is already set and has the specified value" return ret else: if __opts__["test"]: ret["result"] = None ret["comment"] = "The annotation is going to be updated" old = copy.copy(annotations) new = copy.copy(annotations) new[name] = value ret["changes"] = {"old": old, "new": new} return ret ret["comment"] = "The annotation is already set, changing the value" __salt__["kubernetes.node_add_annotation"]( node_name=node, annotation_name=name, annotation_value=value, **kwargs ) old = copy.copy(annotations) annotations[name] = value ret["changes"] = {"old": old, "new": annotations} ret["result"] = True except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
# --------------------------------------------------------------------------- # RBAC states: Role, RoleBinding, ClusterRole, ClusterRoleBinding, # ServiceAccount. # # .. versionadded:: 2.1.0 # # All five kinds share a near-identical present/absent pattern, so the # bulk of the logic lives in :py:func:`_rbac_absent_impl` and # :py:func:`_rbac_present_impl` — the public ``*_present`` / ``*_absent`` # states are thin per-kind wrappers around those helpers. # --------------------------------------------------------------------------- def _rbac_absent_impl(name, kind_lower, kind_pretty, namespaced, namespace, wait, timeout, kwargs): """Shared body for the RBAC *_absent state functions.""" ret = {"name": name, "changes": {}, "result": False, "comment": ""} show_fn = __salt__[f"kubernetes.show_{kind_lower}"] delete_fn = __salt__[f"kubernetes.delete_{kind_lower}"] show_args = (name, namespace) if namespaced else (name,) delete_args = {"wait": wait, "timeout": timeout} if namespaced: delete_args["namespace"] = namespace try: existing = show_fn(*show_args, **kwargs) if existing is None: ret["result"] = True ret["comment"] = f"The {kind_pretty} does not exist" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = f"The {kind_pretty} is going to be deleted" ret["changes"] = {"old": "present", "new": "absent"} return ret delete_fn(name, **delete_args, **kwargs) ret["result"] = True ret["changes"] = {"old": "present", "new": "absent"} ret["comment"] = f"{kind_pretty} {name} deleted" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret def _rbac_present_impl( name, kind_lower, kind_pretty, namespaced, namespace, metadata, spec, source, template, template_context, kwargs, ): """Shared body for the RBAC *_present state functions.""" ret = {"name": name, "changes": {}, "result": False, "comment": ""} if (metadata or spec) and source: return _error(ret, "'source' cannot be used in combination with 'metadata' or 'spec'") if not source and metadata is None: metadata = {} if not source and spec is None: spec = {} show_fn = __salt__[f"kubernetes.show_{kind_lower}"] create_fn = __salt__[f"kubernetes.create_{kind_lower}"] patch_fn = __salt__[f"kubernetes.patch_{kind_lower}"] show_args = (name, namespace) if namespaced else (name,) create_kwargs = { "name": name, "metadata": metadata, "spec": spec, "source": source, "template": template, "saltenv": __env__, "template_context": template_context, "dry_run": bool(__opts__["test"]), } patch_kwargs = {"name": name, "dry_run": bool(__opts__["test"])} if namespaced: create_kwargs["namespace"] = namespace patch_kwargs["namespace"] = namespace try: existing = show_fn(*show_args, **kwargs) if existing is None: try: res = create_fn(**create_kwargs, **kwargs) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( f"The {kind_pretty} is going to be created. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret ret["changes"] = {"old": {}, "new": res} if __opts__["test"]: ret["result"] = None ret["comment"] = f"The {kind_pretty} is going to be created" else: ret["result"] = True ret["comment"] = f"{kind_pretty} created" return ret if source: patch_kwargs.update( {"source": source, "template": template, "template_context": template_context} ) else: patch_obj = {} if metadata: patch_obj["metadata"] = metadata if spec: patch_obj["spec"] = spec patch_kwargs["patch"] = patch_obj try: res = patch_fn(**patch_kwargs, **kwargs) except CommandExecutionError as err: if not __opts__["test"]: raise ret["result"] = None ret["comment"] = ( f"The {kind_pretty} is going to be updated. " f"Dry run failed, possibly due to dependencies not created yet: {err}" ) return ret # ``res == existing`` would always be False because patching # updates ``metadata.resourceVersion`` and ``managedFields`` # even when the user-visible spec is unchanged. Compare after # stripping server-managed bookkeeping so idempotent re-applies # report "already in desired state". if _strip_server_metadata(res) == _strip_server_metadata(existing): ret["result"] = True ret["comment"] = f"The {kind_pretty} is already in the desired state" return ret ret["changes"] = _changes(existing, res) if __opts__["test"]: ret["result"] = None ret["comment"] = f"The {kind_pretty} is going to be updated" else: ret["result"] = True ret["comment"] = f"{kind_pretty} updated" except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
[docs] def role_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """ Ensure the named Role is absent from *namespace*. .. versionadded:: 2.1.0 .. code-block:: yaml pod-reader: kubernetes.role_absent: - namespace: default """ return _rbac_absent_impl(name, "role", "Role", True, namespace, wait, timeout, kwargs)
[docs] def role_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named Role is present with the given rules. .. versionadded:: 2.1.0 .. code-block:: yaml pod-reader: kubernetes.role_present: - namespace: default - spec: rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] """ return _rbac_present_impl( name, "role", "Role", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def role_binding_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named RoleBinding is absent from *namespace*. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "role_binding", "RoleBinding", True, namespace, wait, timeout, kwargs )
[docs] def role_binding_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named RoleBinding exists with the given subjects + roleRef. .. versionadded:: 2.1.0 .. note:: ``roleRef`` is immutable. To change the referenced Role, declare ``role_binding_absent`` first and then ``role_binding_present`` with the new ``roleRef`` — patching ``roleRef`` will be rejected by the API. .. code-block:: yaml read-pods: kubernetes.role_binding_present: - namespace: default - spec: subjects: - kind: User name: alice apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io """ return _rbac_present_impl( name, "role_binding", "RoleBinding", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def cluster_role_absent(name, wait=False, timeout=60, **kwargs): """Ensure the named ClusterRole is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "cluster_role", "ClusterRole", False, None, wait, timeout, kwargs )
[docs] def cluster_role_present( name, metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named ClusterRole is present with the given rules. .. versionadded:: 2.1.0 .. code-block:: yaml pod-reader: kubernetes.cluster_role_present: - spec: rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] """ return _rbac_present_impl( name, "cluster_role", "ClusterRole", False, None, metadata, spec, source, template, template_context, kwargs, )
[docs] def cluster_role_binding_absent(name, wait=False, timeout=60, **kwargs): """Ensure the named ClusterRoleBinding is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "cluster_role_binding", "ClusterRoleBinding", False, None, wait, timeout, kwargs )
[docs] def cluster_role_binding_present( name, metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named ClusterRoleBinding is present. .. versionadded:: 2.1.0 .. note:: ``roleRef`` is immutable; see :py:func:`role_binding_present`. """ return _rbac_present_impl( name, "cluster_role_binding", "ClusterRoleBinding", False, None, metadata, spec, source, template, template_context, kwargs, )
[docs] def service_account_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named ServiceAccount is absent from *namespace*. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "service_account", "ServiceAccount", True, namespace, wait, timeout, kwargs )
[docs] def service_account_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named ServiceAccount is present in *namespace*. .. versionadded:: 2.1.0 .. code-block:: yaml my-sa: kubernetes.service_account_present: - namespace: default - spec: automount_service_account_token: false image_pull_secrets: - name: my-registry-secret """ return _rbac_present_impl( name, "service_account", "ServiceAccount", True, namespace, metadata, spec, source, template, template_context, kwargs, )
# --------------------------------------------------------------------------- # Node lifecycle states (cordon, uncordon, taint, untaint). # # Drain is intentionally NOT exposed as a state: it's an imperative # operation that depends on cluster runtime state (which pods are where) # rather than a desired-state declaration. Use ``kubernetes.drain`` from # an execution call. # # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def node_cordoned(name, **kwargs): """ Ensure the named node is cordoned (unschedulable). .. versionadded:: 2.1.0 .. code-block:: yaml my-node: kubernetes.node_cordoned: [] """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: node = __salt__["kubernetes.node"](name, **kwargs) if node is None: return _error(ret, f"Node {name} not found") currently_unschedulable = bool((node.get("spec") or {}).get("unschedulable", False)) if currently_unschedulable: ret["result"] = True ret["comment"] = "Node is already cordoned" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "Node would be cordoned" ret["changes"] = {"old": "schedulable", "new": "cordoned"} return ret __salt__["kubernetes.cordon"](name, **kwargs) ret["result"] = True ret["comment"] = f"Node {name} cordoned" ret["changes"] = {"old": "schedulable", "new": "cordoned"} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) return ret
[docs] def node_uncordoned(name, **kwargs): """ Ensure the named node is uncordoned (schedulable). .. versionadded:: 2.1.0 """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: node = __salt__["kubernetes.node"](name, **kwargs) if node is None: return _error(ret, f"Node {name} not found") currently_unschedulable = bool((node.get("spec") or {}).get("unschedulable", False)) if not currently_unschedulable: ret["result"] = True ret["comment"] = "Node is already schedulable" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "Node would be uncordoned" ret["changes"] = {"old": "cordoned", "new": "schedulable"} return ret __salt__["kubernetes.uncordon"](name, **kwargs) ret["result"] = True ret["comment"] = f"Node {name} uncordoned" ret["changes"] = {"old": "cordoned", "new": "schedulable"} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) return ret
[docs] def node_tainted(name, key, effect, value=None, **kwargs): """ Ensure the named node has the given taint. .. versionadded:: 2.1.0 .. note:: State name (``name``) is the node name. ``key`` and ``effect`` identify the taint within the node's taint list (matching the Kubernetes taint identity rule of (key, effect) uniqueness). .. code-block:: yaml gpu-node: kubernetes.node_tainted: - key: gpu - effect: NoSchedule - value: "true" """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: node = __salt__["kubernetes.node"](name, **kwargs) if node is None: return _error(ret, f"Node {name} not found") existing = (node.get("spec") or {}).get("taints") or [] match = next( (t for t in existing if t.get("key") == key and t.get("effect") == effect), None, ) if match is not None and match.get("value") == value: ret["result"] = True ret["comment"] = f"Taint {key}={value}:{effect} already present" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = f"Taint {key}={value}:{effect} would be applied" ret["changes"] = {"old": match, "new": {"key": key, "value": value, "effect": effect}} return ret __salt__["kubernetes.taint"](name, key=key, effect=effect, value=value, **kwargs) ret["result"] = True ret["comment"] = f"Taint {key}={value}:{effect} applied" ret["changes"] = {"old": match, "new": {"key": key, "value": value, "effect": effect}} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) return ret
[docs] def node_untainted(name, key, effect=None, **kwargs): """ Ensure the named node does not carry a taint with the given *key*. .. versionadded:: 2.1.0 If *effect* is given, only the taint with matching ``(key, effect)`` is removed; otherwise every taint with this key is removed. """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} try: node = __salt__["kubernetes.node"](name, **kwargs) if node is None: return _error(ret, f"Node {name} not found") existing = (node.get("spec") or {}).get("taints") or [] if effect is None: matches = [t for t in existing if t.get("key") == key] else: matches = [t for t in existing if t.get("key") == key and t.get("effect") == effect] if not matches: ret["result"] = True ret["comment"] = f"No taint with key '{key}' present" return ret if __opts__["test"]: ret["result"] = None ret["comment"] = f"{len(matches)} taint(s) with key '{key}' would be removed" ret["changes"] = {"old": matches, "new": []} return ret __salt__["kubernetes.untaint"](name, key=key, effect=effect, **kwargs) ret["result"] = True ret["comment"] = f"Removed {len(matches)} taint(s) with key '{key}'" ret["changes"] = {"old": matches, "new": []} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) return ret
# --------------------------------------------------------------------------- # Generic manifest states (manifest_present, manifest_absent). # # These wrap kubernetes.apply / kubernetes.delete_manifest and provide # the desired-state semantics Salt callers expect: idempotent reapply # of the same manifest is a no-op; ``test=True`` produces a dry-run # preview via the API server's own validation. # # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def manifest_present( name, source=None, manifest=None, namespace=None, field_manager="salt", force_conflicts=False, template=None, template_context=None, **kwargs, ): """ Ensure one or more Kubernetes objects described by a manifest are present, using server-side apply. .. versionadded:: 2.1.0 The manifest may be a Python dict, a list of dicts, a YAML string, or — via ``source`` — a salt:// fileserver path. Multi-document YAML files are supported; every document in the file is applied as a single state operation. name The state ID. Used as the ``name`` field of the result; not sent to the API. Use whatever identifies the SLS rule for you. source Salt fileserver path to a YAML manifest. Mutually exclusive with ``manifest``. manifest Inline manifest (dict, list of dicts, or YAML string). Mutually exclusive with ``source``. namespace Fallback namespace for namespaced manifests that don't declare their own ``metadata.namespace``. Cluster-scoped kinds ignore. field_manager SSA fieldManager. Default: ``"salt"``. force_conflicts Override fields owned by another field manager. Default: off. template Source-file template engine (e.g. ``"jinja"``). template_context Variables passed to the renderer. .. code-block:: yaml my-app-stack: kubernetes.manifest_present: - source: salt://manifests/my-app.yaml - namespace: production - template: jinja # Or inline: my-config: kubernetes.manifest_present: - manifest: apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: default data: greeting: hello """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if source and manifest is not None: return _error(ret, "'source' and 'manifest' are mutually exclusive") if not source and manifest is None: return _error(ret, "Provide either 'source' or 'manifest'") apply_kwargs = { "manifest": manifest, "source": source, "namespace": namespace, "field_manager": field_manager, "force_conflicts": force_conflicts, "template": template, "saltenv": __env__, "template_context": template_context, } try: if __opts__["test"]: # Real change-detection: run a server-side dry-run apply for # every doc, fetch the current live object, and diff them # against each other (stripping server-set metadata). If # nothing would change, we report ``result=True`` so callers # can rely on state-runs for idempotency. Without this, every # ``test=True`` invocation would always claim a pending # change — defeating the point of idempotency checks. applied = __salt__["kubernetes.apply"](dry_run=True, **apply_kwargs, **kwargs) applied_list = applied if isinstance(applied, list) else [applied] changes = {} for desired in applied_list: live = __salt__["kubernetes.get_object"]( api_version=desired.get("apiVersion"), kind=desired.get("kind"), name=(desired.get("metadata") or {}).get("name"), namespace=(desired.get("metadata") or {}).get("namespace"), ) obj_key = _manifest_key(desired) if live is None: changes[obj_key] = {"old": None, "new": "would create"} elif _strip_server_metadata(desired) != _strip_server_metadata(live): changes[obj_key] = {"old": "present", "new": "would update"} if not changes: ret["result"] = True ret["comment"] = "Manifests already match desired state" return ret ret["result"] = None ret["comment"] = "Manifests would be applied (server-side dry run)" ret["changes"] = changes return ret res = __salt__["kubernetes.apply"](**apply_kwargs, **kwargs) ret["result"] = True ret["comment"] = "Manifests applied via server-side apply" ret["changes"] = {"applied": res} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
def _manifest_key(doc): meta = doc.get("metadata") or {} ns = meta.get("namespace") or "" name = meta.get("name") or "" kind = doc.get("kind") or "" api = doc.get("apiVersion") or "" return f"{api}/{kind}/{ns}/{name}" if ns else f"{api}/{kind}/{name}" _SERVER_METADATA_KEYS = { "resourceVersion", "uid", "generation", "creationTimestamp", "managedFields", "selfLink", "deletionTimestamp", "deletionGracePeriodSeconds", "finalizers", } def _strip_server_metadata(obj): """Return *obj* with API-server-controlled metadata fields removed. Used for diffing a desired manifest against a live object: server bookkeeping fields like ``resourceVersion`` and ``managedFields`` always differ even when nothing meaningful has changed, so they must be excluded from the comparison. """ if not isinstance(obj, dict): return obj out = copy.deepcopy(obj) metadata = out.get("metadata") if isinstance(metadata, dict): for key in _SERVER_METADATA_KEYS: metadata.pop(key, None) out.pop("status", None) return out
[docs] def manifest_absent( name, source=None, manifest=None, namespace=None, propagation_policy=None, grace_period_seconds=None, template=None, template_context=None, **kwargs, ): """ Ensure one or more Kubernetes objects described by a manifest are absent. .. versionadded:: 2.1.0 Accepts the same manifest / source shapes as :py:func:`manifest_present`. .. code-block:: yaml my-app-stack: kubernetes.manifest_absent: - source: salt://manifests/my-app.yaml - propagation_policy: Foreground """ ret = {"name": name, "changes": {}, "result": False, "comment": ""} if source and manifest is not None: return _error(ret, "'source' and 'manifest' are mutually exclusive") if not source and manifest is None: return _error(ret, "Provide either 'source' or 'manifest'") delete_kwargs = { "manifest": manifest, "source": source, "namespace": namespace, "propagation_policy": propagation_policy, "grace_period_seconds": grace_period_seconds, "template": template, "saltenv": __env__, "template_context": template_context, } try: if __opts__["test"]: docs = __salt__["kubernetes.normalise_manifest_input"]( manifest=manifest, source=source, template=template, saltenv=__env__, template_context=template_context, ) changes = {} for desired in docs: live = __salt__["kubernetes.get_object"]( api_version=desired.get("apiVersion"), kind=desired.get("kind"), name=(desired.get("metadata") or {}).get("name"), namespace=(desired.get("metadata") or {}).get("namespace") or namespace, ) if live is not None: changes[_manifest_key(desired)] = {"old": "present", "new": "absent"} if not changes: ret["result"] = True ret["comment"] = "Manifests already absent" return ret ret["result"] = None ret["comment"] = "Manifests would be deleted" ret["changes"] = changes return ret res = __salt__["kubernetes.delete_manifest"](**delete_kwargs, **kwargs) ret["result"] = True ret["comment"] = "Manifests deleted" ret["changes"] = {"deleted": res} except CommandExecutionError as err: log.error(str(err), exc_info_on_loglevel=logging.DEBUG) ret["result"] = False ret["comment"] = str(err) ret["changes"] = {} return ret
# --------------------------------------------------------------------------- # Batch states (job, cron_job) # # Reuses the RBAC present/absent helpers — same pattern: show, create-if- # absent, patch otherwise; delete on absent. Both kinds are namespaced. # # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def job_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named Job is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl(name, "job", "Job", True, namespace, wait, timeout, kwargs)
[docs] def job_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named Job exists with the given pod template. .. versionadded:: 2.1.0 .. note:: Job ``selector`` and most of ``spec.template`` are immutable after creation; if your manifest changes them, the patch will be rejected. For mutable changes (labels, ttlSecondsAfterFinished), the state behaves normally. .. code-block:: yaml my-job: kubernetes.job_present: - namespace: default - spec: template: spec: restartPolicy: Never containers: - name: hello image: busybox command: ["echo", "hi"] """ return _rbac_present_impl( name, "job", "Job", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def cron_job_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named CronJob is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl(name, "cron_job", "CronJob", True, namespace, wait, timeout, kwargs)
[docs] def cron_job_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named CronJob exists. .. versionadded:: 2.1.0 .. code-block:: yaml my-cron: kubernetes.cron_job_present: - namespace: default - spec: schedule: "*/5 * * * *" concurrencyPolicy: Forbid jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: tick image: busybox command: ["echo", "tick"] """ return _rbac_present_impl( name, "cron_job", "CronJob", True, namespace, metadata, spec, source, template, template_context, kwargs, )
# --------------------------------------------------------------------------- # Networking / Autoscaling / Policy states. # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def ingress_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named Ingress is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl(name, "ingress", "Ingress", True, namespace, wait, timeout, kwargs)
[docs] def ingress_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named Ingress is present. .. versionadded:: 2.1.0 .. code-block:: yaml my-ingress: kubernetes.ingress_present: - namespace: default - spec: ingressClassName: nginx rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: my-svc port: number: 80 """ return _rbac_present_impl( name, "ingress", "Ingress", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def horizontal_pod_autoscaler_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named HPA is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "horizontal_pod_autoscaler", "HorizontalPodAutoscaler", True, namespace, wait, timeout, kwargs, )
[docs] def horizontal_pod_autoscaler_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named HPA is present. .. versionadded:: 2.1.0 .. code-block:: yaml my-hpa: kubernetes.horizontal_pod_autoscaler_present: - namespace: default - spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: my-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 """ return _rbac_present_impl( name, "horizontal_pod_autoscaler", "HorizontalPodAutoscaler", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def pod_disruption_budget_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named PDB is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "pod_disruption_budget", "PodDisruptionBudget", True, namespace, wait, timeout, kwargs, )
[docs] def pod_disruption_budget_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named PDB is present. .. versionadded:: 2.1.0 .. note:: PDB ``spec.selector`` is immutable. Changing it will be rejected by the API; declare the PDB absent first if you need a different selector. .. code-block:: yaml my-pdb: kubernetes.pod_disruption_budget_present: - namespace: default - spec: minAvailable: 2 selector: match_labels: app: my-app """ return _rbac_present_impl( name, "pod_disruption_budget", "PodDisruptionBudget", True, namespace, metadata, spec, source, template, template_context, kwargs, )
# --------------------------------------------------------------------------- # Persistent volume states (PV, PVC). # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def persistent_volume_absent(name, wait=False, timeout=60, **kwargs): """Ensure the named PV is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "persistent_volume", "PersistentVolume", False, None, wait, timeout, kwargs, )
[docs] def persistent_volume_present( name, metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named PV is present. .. versionadded:: 2.1.0 .. note:: Most PV fields are immutable after binding (volume source, capacity, accessModes). For an unmanaged-volume migration, declare the PV absent first. .. code-block:: yaml my-pv: kubernetes.persistent_volume_present: - spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce hostPath: path: /var/data/my-pv """ return _rbac_present_impl( name, "persistent_volume", "PersistentVolume", False, None, metadata, spec, source, template, template_context, kwargs, )
[docs] def persistent_volume_claim_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named PVC is absent. .. versionadded:: 2.1.0""" return _rbac_absent_impl( name, "persistent_volume_claim", "PersistentVolumeClaim", True, namespace, wait, timeout, kwargs, )
[docs] def persistent_volume_claim_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """ Ensure the named PVC is present. .. versionadded:: 2.1.0 .. note:: After binding, ``accessModes``, ``selector``, ``volumeName``, and ``storageClassName`` are immutable. ``resources.requests .storage`` can be expanded (only grown) on storage classes with ``allowVolumeExpansion: true``. .. code-block:: yaml my-pvc: kubernetes.persistent_volume_claim_present: - namespace: default - spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard """ return _rbac_present_impl( name, "persistent_volume_claim", "PersistentVolumeClaim", True, namespace, metadata, spec, source, template, template_context, kwargs, )
# --------------------------------------------------------------------------- # First-class state functions for the remaining kinds in #14: # NetworkPolicy, ResourceQuota, LimitRange, PriorityClass, # CustomResourceDefinition. Each delegates to the shared # :py:func:`_rbac_present_impl` / :py:func:`_rbac_absent_impl` pattern. # # .. versionadded:: 2.1.0 # ---------------------------------------------------------------------------
[docs] def network_policy_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named NetworkPolicy is absent. .. versionadded:: 2.1.0 .. code-block:: yaml deny-all: kubernetes.network_policy_absent: - namespace: default """ return _rbac_absent_impl( name, "network_policy", "NetworkPolicy", True, namespace, wait, timeout, kwargs )
[docs] def network_policy_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named NetworkPolicy is present with the given spec. .. versionadded:: 2.1.0 .. code-block:: yaml deny-all: kubernetes.network_policy_present: - namespace: default - spec: podSelector: {} policyTypes: - Ingress - Egress """ return _rbac_present_impl( name, "network_policy", "NetworkPolicy", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def resource_quota_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named ResourceQuota is absent. .. versionadded:: 2.1.0 .. code-block:: yaml team-quota: kubernetes.resource_quota_absent: - namespace: team-a """ return _rbac_absent_impl( name, "resource_quota", "ResourceQuota", True, namespace, wait, timeout, kwargs )
[docs] def resource_quota_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named ResourceQuota is present with the given spec. .. versionadded:: 2.1.0 .. code-block:: yaml team-quota: kubernetes.resource_quota_present: - namespace: team-a - spec: hard: pods: "10" limits.cpu: "4" limits.memory: 4Gi """ return _rbac_present_impl( name, "resource_quota", "ResourceQuota", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def limit_range_absent(name, namespace="default", wait=False, timeout=60, **kwargs): """Ensure the named LimitRange is absent. .. versionadded:: 2.1.0 .. code-block:: yaml mem-defaults: kubernetes.limit_range_absent: - namespace: team-a """ return _rbac_absent_impl( name, "limit_range", "LimitRange", True, namespace, wait, timeout, kwargs )
[docs] def limit_range_present( name, namespace="default", metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named LimitRange is present with the given spec. .. versionadded:: 2.1.0 .. code-block:: yaml mem-defaults: kubernetes.limit_range_present: - namespace: team-a - spec: limits: - type: Container default: memory: 256Mi defaultRequest: memory: 128Mi """ return _rbac_present_impl( name, "limit_range", "LimitRange", True, namespace, metadata, spec, source, template, template_context, kwargs, )
[docs] def priority_class_absent(name, wait=False, timeout=60, **kwargs): """Ensure the named PriorityClass is absent. .. versionadded:: 2.1.0 Cluster-scoped. Pods that reference a deleted PriorityClass keep their existing priority — Kubernetes does not retroactively rewrite pod specs. .. code-block:: yaml high-priority: kubernetes.priority_class_absent: [] """ return _rbac_absent_impl( name, "priority_class", "PriorityClass", False, None, wait, timeout, kwargs )
[docs] def priority_class_present( name, metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named PriorityClass is present. .. versionadded:: 2.1.0 Cluster-scoped. ``value`` and ``globalDefault`` are immutable after creation; changing them in-place will fail. Re-apply with the same values, or delete-and-recreate, for true updates. .. code-block:: yaml high-priority: kubernetes.priority_class_present: - spec: value: 1000000 description: Critical workloads globalDefault: false preemptionPolicy: PreemptLowerPriority """ return _rbac_present_impl( name, "priority_class", "PriorityClass", False, None, metadata, spec, source, template, template_context, kwargs, )
[docs] def custom_resource_definition_absent(name, wait=False, timeout=60, **kwargs): """Ensure the named CustomResourceDefinition is absent. .. versionadded:: 2.1.0 Cluster-scoped. Deletes every instance of the custom resource as a side-effect (the apiserver garbage-collects them via the CRD's deletion). .. code-block:: yaml widgets.example.io: kubernetes.custom_resource_definition_absent: [] """ return _rbac_absent_impl( name, "custom_resource_definition", "CustomResourceDefinition", False, None, wait, timeout, kwargs, )
[docs] def custom_resource_definition_present( name, metadata=None, spec=None, source="", template="", template_context=None, **kwargs, ): """Ensure the named CustomResourceDefinition is present. .. versionadded:: 2.1.0 Use this to declaratively install operator-style CRDs. The CRD becomes available after the apiserver registers and the storage route is wired up; downstream states that create instances should follow it (e.g. via ``require: kubernetes: widgets.example.io``). .. code-block:: yaml widgets.example.io: kubernetes.custom_resource_definition_present: - spec: group: example.io scope: Namespaced names: plural: widgets singular: widget kind: Widget versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object """ return _rbac_present_impl( name, "custom_resource_definition", "CustomResourceDefinition", False, None, metadata, spec, source, template, template_context, kwargs, )