text: """
  Module for running imgadm command on SmartOS
  """
  
  import logging
  
  import salt.utils.json
  import salt.utils.path
  import salt.utils.platform
  
  log = logging.getLogger(__name__)
  
  # Function aliases
  __func_alias__ = {
      "list_installed": "list",
      "update_installed": "update",
      "import_image": "import",
  }
  
  # Define the module's virtual name
  __virtualname__ = "imgadm"
  
  
  def __virtual__():
      """
      Provides imgadm only on SmartOS
      """
      if salt.utils.platform.is_smartos_globalzone() and salt.utils.path.which("imgadm"):
          return __virtualname__
      return (
          False,
          "{} module can only be loaded on SmartOS compute nodes".format(__virtualname__),
      )
  
  
  def _exit_status(retcode, stderr=None):
      """
      Translate exit status of imgadm
      """
      ret = {
          0: "Successful completion.",
          1: "An error occurred." if not stderr else stderr,
          2: "Usage error.",
          3: "Image not installed.",
      }[retcode]
      return ret
  
  
  def _parse_image_meta(image=None, detail=False):
      ret = None
  
      if image and "Error" in image:
          ret = image
      elif image and "manifest" in image and "name" in image["manifest"]:
          name = image["manifest"]["name"]
          version = image["manifest"]["version"]
          os = image["manifest"]["os"]
          description = image["manifest"]["description"]
          published = image["manifest"]["published_at"]
          source = image["source"]
          if image["manifest"]["name"] == "docker-layer":
              # NOTE: skip docker-layer unless it has a docker:repo and docker:tag
              name = None
              docker_repo = None
              docker_tag = None
              for tag in image["manifest"]["tags"]:
                  if tag.startswith("docker:tag:") and image["manifest"]["tags"][tag]:
                      docker_tag = tag.split(":")[-1]
                  elif tag == "docker:repo":
                      docker_repo = image["manifest"]["tags"][tag]
  
              if docker_repo and docker_tag:
                  name = "{}:{}".format(docker_repo, docker_tag)
                  description = "Docker image imported from {repo}:{tag} on {date}.".format(
                      repo=docker_repo, tag=docker_tag, date=published,
                  )
  
          if name and detail:
              ret = {
                  "name": name,
                  "version": version,
                  "os": os,
                  "description": description,
                  "published": published,
                  "source": source,
              }
          elif name:
              ret = "{name}@{version} [{published}]".format(
                  name=name, version=version, published=published,
              )
      else:
          log.debug("smartos_image - encountered invalid image payload: %s", image)
          ret = {"Error": "This looks like an orphaned image, image payload was invalid."}
  
      return ret
  
  
  def _split_docker_uuid(uuid):
      """
      Split a smartos docker uuid into repo and tag
      """
      if uuid:
          uuid = uuid.split(":")
          if len(uuid) == 2:
              tag = uuid[1]
              repo = uuid[0]
              return repo, tag
      return None, None
  
  
  def _is_uuid(uuid):
      """
      Check if uuid is a valid smartos uuid
  
      Example: e69a0918-055d-11e5-8912-e3ceb6df4cf8
      """
      if uuid and list(len(x) for x in uuid.split("-")) == [8, 4, 4, 4, 12]:
          return True
      return False
  
  
  def _is_docker_uuid(uuid):
      """
      Check if uuid is a valid smartos docker uuid
  
      Example plexinc/pms-docker:plexpass
      """
      repo, tag = _split_docker_uuid(uuid)
      return not (not repo and not tag)
  
  
  def version():
      """
      Return imgadm version
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.version
      """
      ret = {}
      cmd = "imgadm --version"
      res = __salt__["cmd.run"](cmd).splitlines()
      ret = res[0].split()
      return ret[-1]
  
  
  def docker_to_uuid(uuid):
      """
      Get the image uuid from an imported docker image
  
      .. versionadded:: 2019.2.0
      """
      if _is_uuid(uuid):
          return uuid
      if _is_docker_uuid(uuid):
          images = list_installed(verbose=True)
          for image_uuid in images:
              if "name" not in images[image_uuid]:
                  continue
              if images[image_uuid]["name"] == uuid:
                  return image_uuid
      return None
  
  
  def update_installed(uuid=""):
      """
      Gather info on unknown image(s) (locally installed)
  
      uuid : string
          optional uuid of image
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.update [uuid]
      """
      cmd = "imgadm update {}".format(uuid).rstrip()
      __salt__["cmd.run"](cmd)
      return {}
  
  
  def avail(search=None, verbose=False):
      """
      Return a list of available images
  
      search : string
          search keyword
      verbose : boolean (False)
          toggle verbose output
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.avail [percona]
          salt '*' imgadm.avail verbose=True
      """
      ret = {}
      cmd = "imgadm avail -j"
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
  
      for image in salt.utils.json.loads(res["stdout"]):
          if image["manifest"]["disabled"] or not image["manifest"]["public"]:
              continue
          if search and search not in image["manifest"]["name"]:
              # we skip if we are searching but don't have a match
              continue
          uuid = image["manifest"]["uuid"]
          data = _parse_image_meta(image, verbose)
          if data:
              ret[uuid] = data
  
      return ret
  
  
  def list_installed(verbose=False):
      """
      Return a list of installed images
  
      verbose : boolean (False)
          toggle verbose output
  
      .. versionchanged:: 2019.2.0
  
          Docker images are now also listed
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.list
          salt '*' imgadm.list docker=True
          salt '*' imgadm.list verbose=True
      """
      ret = {}
      cmd = "imgadm list -j"
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
  
      for image in salt.utils.json.loads(res["stdout"]):
          uuid = image["manifest"]["uuid"]
          data = _parse_image_meta(image, verbose)
          if data:
              ret[uuid] = data
  
      return ret
  
  
  def show(uuid):
      """
      Show manifest of a given image
  
      uuid : string
          uuid of image
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.show e42f8c84-bbea-11e2-b920-078fab2aab1f
          salt '*' imgadm.show plexinc/pms-docker:plexpass
      """
      ret = {}
  
      if _is_uuid(uuid) or _is_docker_uuid(uuid):
          cmd = "imgadm show {}".format(uuid)
          res = __salt__["cmd.run_all"](cmd, python_shell=False)
          retcode = res["retcode"]
          if retcode != 0:
              ret["Error"] = _exit_status(retcode, res["stderr"])
          else:
              ret = salt.utils.json.loads(res["stdout"])
      else:
          ret["Error"] = "{} is not a valid uuid.".format(uuid)
  
      return ret
  
  
  def get(uuid):
      """
      Return info on an installed image
  
      uuid : string
          uuid of image
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.get e42f8c84-bbea-11e2-b920-078fab2aab1f
          salt '*' imgadm.get plexinc/pms-docker:plexpass
      """
      ret = {}
  
      if _is_docker_uuid(uuid):
          uuid = docker_to_uuid(uuid)
  
      if _is_uuid(uuid):
          cmd = "imgadm get {}".format(uuid)
          res = __salt__["cmd.run_all"](cmd, python_shell=False)
          retcode = res["retcode"]
          if retcode != 0:
              ret["Error"] = _exit_status(retcode, res["stderr"])
          else:
              ret = salt.utils.json.loads(res["stdout"])
      else:
          ret["Error"] = "{} is not a valid uuid.".format(uuid)
  
      return ret
  
  
  def import_image(uuid, verbose=False):
      """
      Import an image from the repository
  
      uuid : string
          uuid to import
      verbose : boolean (False)
          toggle verbose output
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.import e42f8c84-bbea-11e2-b920-078fab2aab1f [verbose=True]
      """
      ret = {}
      cmd = "imgadm import {}".format(uuid)
      res = __salt__["cmd.run_all"](cmd, python_shell=False)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
  
      uuid = docker_to_uuid(uuid)
      data = _parse_image_meta(get(uuid), verbose)
      return {uuid: data}
  
  
  def delete(uuid):
      """
      Remove an installed image
  
      uuid : string
          Specifies uuid to import
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.delete e42f8c84-bbea-11e2-b920-078fab2aab1f
      """
      ret = {}
      cmd = "imgadm delete {}".format(uuid)
      res = __salt__["cmd.run_all"](cmd, python_shell=False)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
      # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc
      result = []
      for image in res["stdout"].splitlines():
          image = [var for var in image.split(" ") if var]
          result.append(image[2])
  
      return result
  
  
  def vacuum(verbose=False):
      """
      Remove unused images
  
      verbose : boolean (False)
          toggle verbose output
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.vacuum [verbose=True]
      """
      ret = {}
      cmd = "imgadm vacuum -f"
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
      # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc (lx-centos-6@20150601)
      result = {}
      for image in res["stdout"].splitlines():
          image = [var for var in image.split(" ") if var]
          result[image[2]] = {
              "name": image[3][1 : image[3].index("@")],
              "version": image[3][image[3].index("@") + 1 : -1],
          }
      if verbose:
          return result
      else:
          return list(result.keys())
  
  
  def sources(verbose=False):
      """
      Return a list of available sources
  
      verbose : boolean (False)
          toggle verbose output
  
      .. versionadded:: 2019.2.0
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.sources
      """
      ret = {}
      cmd = "imgadm sources -j"
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode)
          return ret
  
      for src in salt.utils.json.loads(res["stdout"]):
          ret[src["url"]] = src
          del src["url"]
  
      if not verbose:
          ret = list(ret)
  
      return ret
  
  
  def source_delete(source):
      """
      Delete a source
  
      source : string
          source url to delete
  
      .. versionadded:: 2019.2.0
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.source_delete https://updates.joyent.com
      """
      ret = {}
      cmd = "imgadm sources -d {}".format(source)
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode, res["stderr"])
          return ret
  
      return sources(False)
  
  
  def source_add(source, source_type="imgapi"):
      """
      Add a new source
  
      source : string
          source url to add
      source_trype : string (imgapi)
          source type, either imgapi or docker
  
      .. versionadded:: 2019.2.0
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' imgadm.source_add https://updates.joyent.com
          salt '*' imgadm.source_add https://docker.io docker
      """
      ret = {}
  
      # NOTE: there are some undocumented deprecated source types
      #       so we just warn instead of error on those
      if source_type not in ["imgapi", "docker"]:
          log.warning("Possible unsupported imgage source type specified!")
  
      cmd = "imgadm sources -a {} -t {}".format(source, source_type)
      res = __salt__["cmd.run_all"](cmd)
      retcode = res["retcode"]
      if retcode != 0:
          ret["Error"] = _exit_status(retcode, res["stderr"])
          return ret
  
      return sources(False)
  
  
  # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
  