Source code for saltext.github.utils.gh.statecores.environments

"""
State core managing repository (deployment) environments.
"""

import logging

from salt.exceptions import CommandExecutionError

from saltext.github.utils import filter_unset
from saltext.github.utils.gh import environments as gh_environments
from saltext.github.utils.gh.exceptions import GitHubNotFoundError
from saltext.github.utils.gh.statecores import StateError

log = logging.getLogger(__name__)


def _environment_settings_diff(ret, current_settings, desired):
    """
    Record an existing environment's settings that need updating in
    ``ret`` and return whether there are any.
    """
    diff = {}
    for key, val in desired.items():
        current_val = current_settings.get(key)
        if key == "reviewers":
            matches = sorted((reviewer["type"], reviewer["id"]) for reviewer in val) == sorted(
                (reviewer["type"], reviewer["id"]) for reviewer in current_val
            )
        else:
            matches = current_val == val
        if not matches:
            diff[key] = val
    if diff:
        ret["changes"]["old"] = {key: current_settings.get(key) for key in diff}
        ret["changes"]["new"] = diff
    return bool(diff)


[docs] def present( conn, name, repo_name, *, wait_timer=None, prevent_self_review=None, reviewers=None, deployment_branches=None, owner=None, test=False, ): """ Ensure an environment is present in a repository and configured as specified. Unspecified settings are left unmanaged. conn :class:`~saltext.github.utils.gh.client.GitHubConnection` to use. name Name of the environment. repo_name Name of the repository. wait_timer Amount of time to delay a job after it is initially triggered, in minutes. prevent_self_review Prevent a user who created a job from approving it. reviewers People or teams that may review jobs that reference the environment, as a list of mappings with ``type`` (``User`` or ``Team``) and ``id``. deployment_branches Which branches can deploy to this environment. Either ``all``, ``protected`` (branches with branch protection rules) or a list of branch name patterns. owner Owner of the repository. Defaults to the connection's owner. test Only project the changes without applying them (``test`` mode). """ ret = { "name": name, "changes": {}, "result": True, "comment": f"Environment {name} is configured as specified", } try: current = gh_environments.get(conn, repo_name, name, owner=owner) except GitHubNotFoundError: current = None desired = filter_unset( { "wait_timer": wait_timer, "prevent_self_review": prevent_self_review, "reviewers": reviewers, } ) if deployment_branches is not None: desired["deployment_branch_policy"] = gh_environments.branch_policy_from_patterns( deployment_branches ) settings_diff = False branches_added, branches_removed, current_policies = [], [], [] if current is None: ret["changes"]["created"] = name set_kwargs = desired if isinstance(deployment_branches, list): branches_added = sorted(set(deployment_branches)) else: current_settings = gh_environments.normalize(current) settings_diff = _environment_settings_diff(ret, current_settings, desired) # The API call replaces the whole configuration, so settings # that are not managed here need to be carried over. set_kwargs = {**current_settings, **desired} if isinstance(deployment_branches, list): try: current_policies = gh_environments.list_branch_policies( conn, repo_name, name, owner=owner ) except GitHubNotFoundError: # Not found when the current policy type is not "custom" current_policies = [] current_patterns = {policy["name"] for policy in current_policies} branches_added = sorted(set(deployment_branches) - current_patterns) branches_removed = sorted(current_patterns - set(deployment_branches)) if branches_added or branches_removed: ret["changes"]["deployment_branches"] = { "added": branches_added, "removed": branches_removed, } if not ret["changes"]: return ret verb = "created" if current is None else "updated" if test: ret["result"] = None ret["comment"] = f"Environment {name} would have been {verb}" return ret applied = {} try: if current is None or settings_diff: gh_environments.set_(conn, repo_name, name, owner=owner, **set_kwargs) if current is None: applied["created"] = name else: applied["old"] = ret["changes"]["old"] applied["new"] = ret["changes"]["new"] for pattern in branches_added: gh_environments.create_branch_policy(conn, repo_name, name, pattern, owner=owner) applied.setdefault("deployment_branches", {}).setdefault("added", []).append(pattern) for policy in current_policies: if policy["name"] in branches_removed: gh_environments.delete_branch_policy( conn, repo_name, name, policy["id"], owner=owner ) applied.setdefault("deployment_branches", {}).setdefault("removed", []).append( policy["name"] ) except CommandExecutionError as err: raise StateError(str(err), changes=applied) from err ret["comment"] = f"Environment {name} has been {verb}" return ret
[docs] def absent(conn, name, repo_name, *, owner=None, test=False): """ Ensure an environment is absent from a repository. conn :class:`~saltext.github.utils.gh.client.GitHubConnection` to use. name Name of the environment. repo_name Name of the repository. owner Owner of the repository. Defaults to the connection's owner. test Only project the changes without applying them (``test`` mode). """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} try: gh_environments.get(conn, repo_name, name, owner=owner) except GitHubNotFoundError: ret["comment"] = f"Environment {name} is already absent" return ret ret["changes"]["deleted"] = name if test: ret["result"] = None ret["comment"] = f"Environment {name} would have been removed" return ret gh_environments.delete(conn, repo_name, name, owner=owner) ret["comment"] = f"Environment {name} has been removed" return ret