PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/heat.py |
""" Module for handling OpenStack Heat calls .. versionadded:: 2017.7.0 :depends: - heatclient Python module :configuration: This module is not usable until the user, password, tenant, and auth URL are specified either in a pillar or in the minion's config file. For example:: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.insecure: False #(optional) keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' # Optional keystone.region_name: 'RegionOne' If configuration for multiple OpenStack accounts is required, they can be set up as different configuration profiles: For example:: openstack1: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' openstack2: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' With this configuration in place, any of the heat functions can make use of a configuration profile by declaring it explicitly. For example:: salt '*' heat.flavor_list profile=openstack1 """ import logging import time import salt.utils.files import salt.utils.json import salt.utils.stringutils import salt.utils.versions import salt.utils.yaml from salt.exceptions import SaltInvocationError # pylint: disable=import-error HAS_HEAT = False try: import heatclient HAS_HEAT = True except ImportError: pass HAS_OSLO = False try: from oslo_serialization import jsonutils HAS_OSLO = True except ImportError: pass SECTIONS = (PARAMETER_DEFAULTS, PARAMETERS, RESOURCE_REGISTRY, EVENT_SINKS) = ( "parameter_defaults", "parameters", "resource_registry", "event_sinks", ) logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) def __virtual__(): """ Only load this module if heat is installed on this minion. """ if HAS_HEAT and HAS_OSLO: return "heat" return ( False, "The heat execution module cannot be loaded: " "the heatclient and oslo_serialization" # 'the heatclient and keystoneclient and oslo_serialization' " python library is not available.", ) def _auth(profile=None, api_version=1, **connection_args): """ Set up heat credentials, returns `heatclient.client.Client`. Optional parameter "api_version" defaults to 1. Only intended to be used within heat-enabled modules """ if profile: prefix = profile + ":keystone." else: prefix = "keystone." def get(key, default=None): """ Checks connection_args, then salt-minion config, falls back to specified default value. """ return connection_args.get( "connection_" + key, __salt__["config.get"](prefix + key, default) ) user = get("user", "admin") password = get("password", None) tenant = get("tenant", "admin") tenant_id = get("tenant_id") auth_url = get("auth_url", "http://127.0.0.1:35357/v2.0") insecure = get("insecure", False) admin_token = get("token") region_name = get("region_name", None) if admin_token and api_version != 1 and not password: # If we had a password we could just # ignore the admin-token and move on... raise SaltInvocationError( "Only can use keystone admin token " + "with Heat API v1" ) elif password: # Can't use the admin-token anyway kwargs = { "username": user, "password": password, "tenant_id": tenant_id, "auth_url": auth_url, "region_name": region_name, "tenant_name": tenant, } # 'insecure' keyword not supported by all v2.0 keystone clients # this ensures it's only passed in when defined if insecure: kwargs["insecure"] = True elif api_version == 1 and admin_token: kwargs = {"token": admin_token, "auth_url": auth_url} else: raise SaltInvocationError("No credentials to authenticate with.") token = __salt__["keystone.token_get"](profile) kwargs["token"] = token["id"] # This doesn't realy prevent the password to show up # in the minion log as keystoneclient.session is # logging it anyway when in debug-mode kwargs.pop("password") try: heat_endpoint = __salt__["keystone.endpoint_get"]("heat", profile)["url"] except KeyError: heat_endpoint = __salt__["keystone.endpoint_get"]("heat", profile)["publicurl"] heat_endpoint = heat_endpoint % token log.debug( "Calling heatclient.client.Client(%s, %s, **%s)", api_version, heat_endpoint, kwargs, ) # may raise exc.HTTPUnauthorized, exc.HTTPNotFound # but we deal with those elsewhere return heatclient.client.Client(api_version, endpoint=heat_endpoint, **kwargs) def _parse_template(tmpl_str): """ Parsing template """ tmpl_str = tmpl_str.strip() if tmpl_str.startswith("{"): tpl = salt.utils.json.loads(tmpl_str) else: try: tpl = salt.utils.yaml.safe_load(tmpl_str) except salt.utils.yaml.YAMLError as exc: raise ValueError(str(exc)) else: if tpl is None: tpl = {} if not ( "HeatTemplateFormatVersion" in tpl or "heat_template_version" in tpl or "AWSTemplateFormatVersion" in tpl ): raise ValueError("Template format version not found.") return tpl def _parse_environment(env_str): """ Parsing template """ try: env = salt.utils.yaml.safe_load(env_str) except salt.utils.yaml.YAMLError as exc: raise ValueError(str(exc)) else: if env is None: env = {} elif not isinstance(env, dict): raise ValueError("The environment is not a valid YAML mapping data type.") for param in env: if param not in SECTIONS: raise ValueError(f'environment has wrong section "{param}"') return env def _get_stack_events(h_client, stack_id, event_args): """ Get event for stack """ event_args["stack_id"] = stack_id event_args["resource_name"] = None try: events = h_client.events.list(**event_args) except heatclient.exc.HTTPNotFound as exc: raise heatclient.exc.CommandError(str(exc)) else: for event in events: event.stack_name = stack_id.split("/")[0] return events def _poll_for_events( h_client, stack_name, action=None, poll_period=5, timeout=60, marker=None ): """ Polling stack events """ if action: stop_status = (f"{action}_FAILED", f"{action}_COMPLETE") def stop_check(a): return a in stop_status else: def stop_check(a): return a.endswith("_COMPLETE") or a.endswith("_FAILED") timeout_sec = timeout * 60 no_event_polls = 0 msg_template = "\n Stack %(name)s %(status)s \n" while True: events = _get_stack_events( h_client, stack_id=stack_name, event_args={"sort_dir": "asc", "marker": marker}, ) if not events: no_event_polls += 1 else: no_event_polls = 0 # set marker to last event that was received. marker = getattr(events[-1], "id", None) for event in events: # check if stack event was also received if getattr(event, "resource_name", "") == stack_name: stack_status = getattr(event, "resource_status", "") msg = msg_template % dict(name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg if no_event_polls >= 2: # after 2 polls with no events, fall back to a stack get stack = h_client.stacks.get(stack_name) stack_status = stack.stack_status msg = msg_template % dict(name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg # go back to event polling again no_event_polls = 0 time.sleep(poll_period) timeout_sec -= poll_period if timeout_sec <= 0: stack_status = f"{action}_FAILED" msg = "Timeout expired" return stack_status, msg def list_stack(profile=None): """ Return a list of available stack (heat stack-list) profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.list_stack profile=openstack1 """ ret = {} h_client = _auth(profile) for stack in h_client.stacks.list(): links = {} for link in stack.links: links[link["rel"]] = link["href"] ret[stack.stack_name] = { "status": stack.stack_status, "id": stack.id, "name": stack.stack_name, "creation": stack.creation_time, "owner": stack.stack_owner, "reason": stack.stack_status_reason, "links": links, } return ret def show_stack(name=None, profile=None): """ Return details about a specific stack (heat stack-show) name Name of the stack profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.show_stack name=mystack profile=openstack1 """ h_client = _auth(profile) if not name: return {"result": False, "comment": "Parameter name missing or None"} try: ret = {} stack = h_client.stacks.get(name) links = {} for link in stack.links: links[link["rel"]] = link["href"] ret[stack.stack_name] = { "status": stack.stack_status, "id": stack.id, "name": stack.stack_name, "creation": stack.creation_time, "owner": stack.stack_owner, "reason": stack.stack_status_reason, "parameters": stack.parameters, "links": links, } ret["result"] = True except heatclient.exc.HTTPNotFound: return {"result": False, "comment": f"No stack {name}"} return ret def delete_stack(name=None, poll=0, timeout=60, profile=None): """ Delete a stack (heat stack-delete) name Name of the stack poll Poll and report events until stack complete timeout Stack creation timeout in minute profile Profile to use CLI Examples: .. code-block:: bash salt '*' heat.delete_stack name=mystack poll=5 \\ profile=openstack1 """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not name: ret["result"] = False ret["comment"] = "Parameter name missing or None" return ret try: h_client.stacks.delete(name) except heatclient.exc.HTTPNotFound: ret["result"] = False ret["comment"] = f"No stack {name}" except heatclient.exc.HTTPForbidden as forbidden: log.exception(forbidden) ret["result"] = False ret["comment"] = str(forbidden) if ret["result"] is False: return ret if poll > 0: try: stack_status, msg = _poll_for_events( h_client, name, action="DELETE", poll_period=poll, timeout=timeout ) except heatclient.exc.CommandError: ret["comment"] = f"Deleted stack {name}." return ret except Exception as ex: # pylint: disable=W0703 log.exception("Delete failed %s", ex) ret["result"] = False ret["comment"] = f"{ex}" return ret if stack_status == "DELETE_FAILED": ret["result"] = False ret["comment"] = f"Deleted stack FAILED'{name}'{msg}." else: ret["comment"] = f"Deleted stack {name}." return ret def create_stack( name=None, template_file=None, environment=None, parameters=None, poll=0, rollback=False, timeout=60, profile=None, ): """ Create a stack (heat stack-create) name Name of the new stack template_file File of template environment File of environment parameters Parameter dict used to create the stack poll Poll and report events until stack complete rollback Enable rollback on create failure timeout Stack creation timeout in minutes profile Profile to build on CLI Example: .. code-block:: bash salt '*' heat.create_stack name=mystack \\ template_file=salt://template.yaml \\ environment=salt://environment.yaml \\ parameters="{"image": "Debian 8", "flavor": "m1.small"}" \\ poll=5 rollback=False timeout=60 profile=openstack1 .. versionadded:: 2017.7.5,2018.3.1 The spelling mistake in parameter `enviroment` was corrected to `environment`. The `enviroment` spelling mistake has been removed in Salt 3000. """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not parameters: parameters = {} if template_file: template_tmp_file = salt.utils.files.mkstemp() tsfn, source_sum, comment_ = __salt__["file.get_managed"]( name=template_tmp_file, template=None, source=template_file, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) template_manage_result = __salt__["file.manage_file"]( name=template_tmp_file, sfn=tsfn, ret=None, source=template_file, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if template_manage_result["result"]: with salt.utils.files.fopen(template_tmp_file, "r") as tfp_: tpl = salt.utils.stringutils.to_unicode(tfp_.read()) salt.utils.files.safe_rm(template_tmp_file) try: template = _parse_template(tpl) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open template: {} {}".format( template_file, comment_ ) else: ret["result"] = False ret["comment"] = "Can not open template" if ret["result"] is False: return ret kwargs = {} kwargs["template"] = template try: h_client.stacks.validate(**kwargs) except Exception as ex: # pylint: disable=W0703 log.exception("Template not valid %s", ex) ret["result"] = False ret["comment"] = f"Template not valid {ex}" return ret env = {} if environment: environment_tmp_file = salt.utils.files.mkstemp() esfn, source_sum, comment_ = __salt__["file.get_managed"]( name=environment_tmp_file, template=None, source=environment, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) environment_manage_result = __salt__["file.manage_file"]( name=environment_tmp_file, sfn=esfn, ret=None, source=environment, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if environment_manage_result["result"]: with salt.utils.files.fopen(environment_tmp_file, "r") as efp_: env_str = salt.utils.stringutils.to_unicode(efp_.read()) salt.utils.files.safe_rm(environment_tmp_file) try: env = _parse_environment(env_str) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open environment: {}, {}".format( environment, comment_ ) if ret["result"] is False: return ret fields = { "stack_name": name, "disable_rollback": not rollback, "parameters": parameters, "template": template, "environment": env, "timeout_mins": timeout, } # If one or more environments is found, pass the listing to the server try: h_client.stacks.create(**fields) except Exception as ex: # pylint: disable=W0703 log.exception("Create failed %s", ex) ret["result"] = False ret["comment"] = f"{ex}" return ret if poll > 0: stack_status, msg = _poll_for_events( h_client, name, action="CREATE", poll_period=poll, timeout=timeout ) if stack_status == "CREATE_FAILED": ret["result"] = False ret["comment"] = f"Created stack FAILED'{name}'{msg}." if ret["result"] is True: ret["comment"] = f"Created stack '{name}'." return ret def update_stack( name=None, template_file=None, environment=None, parameters=None, poll=0, rollback=False, timeout=60, profile=None, ): """ Update a stack (heat stack-template) name Name of the stack template_file File of template environment File of environment parameters Parameter dict used to update the stack poll Poll and report events until stack complete rollback Enable rollback on update failure timeout Stack creation timeout in minutes profile Profile to build on CLI Example: .. code-block:: bash salt '*' heat.update_stack name=mystack \\ template_file=salt://template.yaml \\ environment=salt://environment.yaml \\ parameters="{"image": "Debian 8", "flavor": "m1.small"}" \\ poll=5 rollback=False timeout=60 profile=openstack1 .. versionadded:: 2017.7.5,2018.3.1 The spelling mistake in parameter `enviroment` was corrected to `environment`. The `enviroment` spelling mistake has been removed in Salt 3000. """ h_client = _auth(profile) ret = {"result": True, "comment": ""} if not name: ret["result"] = False ret["comment"] = "Parameter name missing or None" return ret if not parameters: parameters = {} if template_file: template_tmp_file = salt.utils.files.mkstemp() tsfn, source_sum, comment_ = __salt__["file.get_managed"]( name=template_tmp_file, template=None, source=template_file, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) template_manage_result = __salt__["file.manage_file"]( name=template_tmp_file, sfn=tsfn, ret=None, source=template_file, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if template_manage_result["result"]: with salt.utils.files.fopen(template_tmp_file, "r") as tfp_: tpl = salt.utils.stringutils.to_unicode(tfp_.read()) salt.utils.files.safe_rm(template_tmp_file) try: template = _parse_template(tpl) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open template: {} {}".format( template_file, comment_ ) else: ret["result"] = False ret["comment"] = "Can not open template" if ret["result"] is False: return ret kwargs = {} kwargs["template"] = template try: h_client.stacks.validate(**kwargs) except Exception as ex: # pylint: disable=W0703 log.exception("Template not valid %s", ex) ret["result"] = False ret["comment"] = f"Template not valid {ex}" return ret env = {} if environment: environment_tmp_file = salt.utils.files.mkstemp() esfn, source_sum, comment_ = __salt__["file.get_managed"]( name=environment_tmp_file, template=None, source=environment, source_hash=None, source_hash_name=None, user=None, group=None, mode=None, attrs=None, saltenv="base", context=None, defaults=None, skip_verify=False, kwargs=None, ) environment_manage_result = __salt__["file.manage_file"]( name=environment_tmp_file, sfn=esfn, ret=None, source=environment, source_sum=source_sum, user=None, group=None, mode=None, attrs=None, saltenv="base", backup=None, makedirs=True, template=None, show_changes=False, contents=None, dir_mode=None, ) if environment_manage_result["result"]: with salt.utils.files.fopen(environment_tmp_file, "r") as efp_: env_str = salt.utils.stringutils.to_unicode(efp_.read()) salt.utils.files.safe_rm(environment_tmp_file) try: env = _parse_environment(env_str) except ValueError as ex: ret["result"] = False ret["comment"] = f"Error parsing template {ex}" else: ret["result"] = False ret["comment"] = "Can not open environment: {}, {}".format( environment, comment_ ) if ret["result"] is False: return ret fields = { "disable_rollback": not rollback, "parameters": parameters, "template": template, "environment": env, "timeout_mins": timeout, } try: h_client.stacks.update(name, **fields) except Exception as ex: # pylint: disable=W0703 log.exception("Update failed %s", ex) ret["result"] = False ret["comment"] = f"Update failed {ex}" return ret if poll > 0: stack_status, msg = _poll_for_events( h_client, name, action="UPDATE", poll_period=poll, timeout=timeout ) if stack_status == "UPDATE_FAILED": ret["result"] = False ret["comment"] = f"Updated stack FAILED'{name}'{msg}." if ret["result"] is True: ret["comment"] = (f"Updated stack '{name}'.",) return ret def template_stack(name=None, profile=None): """ Return template a specific stack (heat stack-template) name Name of the stack profile Profile to use CLI Example: .. code-block:: bash salt '*' heat.template_stack name=mystack profile=openstack1 """ h_client = _auth(profile) if not name: return {"result": False, "comment": "Parameter name missing or None"} try: get_template = h_client.stacks.template(name) except heatclient.exc.HTTPNotFound: return {"result": False, "comment": f"No stack with {name}"} except heatclient.exc.BadRequest: return {"result": False, "comment": f"Bad request fot stack {name}"} if "heat_template_version" in get_template: template = salt.utils.yaml.safe_dump(get_template) else: template = jsonutils.dumps(get_template, indent=2, ensure_ascii=False) checksum = __salt__["hashutil.digest"](template) ret = {"template": template, "result": True, "checksum": checksum} return ret