text: import sys
  import json
  import click
  import logging
  import time
  import base64
  
  import anchorecli.clients.apiexternal
  import anchorecli.cli.utils
  
  config = {}
  _logger = logging.getLogger(__name__)
  
  @click.group(name='image', short_help='Image operations')
  @click.pass_obj
  def image(ctx_config):
      global config
      config = ctx_config
  
      try:
          anchorecli.cli.utils.check_access(config)
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image', {}, err))
          sys.exit(2)
  
  
  @image.command(short_help="Wait for an image to analyze")
  @click.argument('input_image')
  @click.option('--timeout', type=float, default=-1.0, help="Time to wait, in seconds.  If < 0, wait forever, if 0, do not wait (default=-1)")
  @click.option('--interval', type=float, default=5.0, help="Interval between checks, in seconds (default=5)")
  def wait(input_image, timeout, interval):
      """
      Wait for an image to go to analyzed or analysis_failed status with a specific timeout
  
      :param input_image:
      :param timeout:
      :return:
      """
      ecode = 0
  
      try:
          itype = anchorecli.cli.utils.discover_inputimage_format(config, input_image)
          image = input_image
          #timeout = float(timeout)
          t1 = time.time()
          while timeout < 0 or time.time() - t1 < timeout:
              _logger.debug("discovery from input: %s : %s",  str(itype), str(image))
              if itype == 'tag':
                  ret = anchorecli.clients.apiexternal.get_image(config, tag=image, history=False)
              elif itype == 'imageid':
                  ret = anchorecli.clients.apiexternal.get_image(config, image_id=image, history=False)
              elif itype == 'imageDigest':
                  ret = anchorecli.clients.apiexternal.get_image(config, imageDigest=image, history=False)
              else:
                  ecode = 1
                  raise Exception("cannot use input image string: no discovered imageDigest")
  
              if ret['payload'] and ret['payload'][0]['analysis_status'] in ['analyzed', 'analysis_failed']:
                  break
              else:
                  if not ret['payload']:
                      raise Exception('Requested image not found in system')
                  print('Status: {}'.format(ret['payload'][0]['analysis_status']))
  
              if timeout > 0:
                  print('Waiting {} seconds for next retry. Total timeout remaining: {}'.format(interval, int(timeout - (time.time() - t1))))
              else:
                  print('Waiting {} seconds for next retry.'.format(interval))
  
              time.sleep(interval)
          else:
              raise Exception('Timed-out waiting for analyis status to reach terminal state (analyzed or analysis_failed)')
  
          if ret:
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret['success']:
                  print(anchorecli.cli.utils.format_output(config, 'image_get', {}, ret['payload']))
              else:
                  raise Exception(json.dumps(ret['error'], indent=4))
          else:
              raise Exception("operation failed with empty response")
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_get', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  
  @image.command(name='add', short_help="Add an image")
  @click.argument('input_image', nargs=1)
  @click.option('--force', is_flag=True, help="Force reanalysis of image")
  @click.option('--dockerfile', type=click.Path(exists=True), metavar='<Dockerfile>', help="Submit image's dockerfile for analysis")
  @click.option('--annotation', nargs=1, multiple=True)
  @click.option('--noautosubscribe', is_flag=True, help="If set, instruct the engine to disable tag_update subscription for the added tag.")
  def add(input_image, force, dockerfile, annotation, noautosubscribe):
      """
      INPUT_IMAGE: Input image can be in the following formats: registry/repo:tag
      """
      ecode = 0
  
      try:
          itype = anchorecli.cli.utils.discover_inputimage_format(config, input_image)
  
          dockerfile_contents = None
          if dockerfile:
              with open(dockerfile, 'r') as FH:
                  dockerfile_contents = base64.b64encode(FH.read().encode('utf-8')).decode('utf-8')
  
          autosubscribe = not noautosubscribe
  
          if itype == 'tag':
              annotations = {}
              if annotation:
                  for a in annotation:
                      try:
                          (k,v) = a.split('=', 1)
                          if k and v:
                              annotations[k] = v
                          else:
                              raise Exception("found null in key or value")
                      except Exception:
                          raise Exception("annotation format error - annotations must be of the form (--annotation key=value), found: {}".format(a))
  
              ret = anchorecli.clients.apiexternal.add_image(config, tag=input_image, force=force, dockerfile=dockerfile_contents, annotations=annotations, autosubscribe=autosubscribe)
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret['success']:
                  print(anchorecli.cli.utils.format_output(config, 'image_add', {}, ret['payload']))
              else:
                  raise Exception( json.dumps(ret['error'], indent=4))
          else:
              raise Exception("can only add a tag")
  
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_add', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  @image.command(name='import', short_help="Import an image from anchore scanner export")
  @click.option('--infile', required=True, type=click.Path(exists=True), metavar='<file.json>')
  def import_image(infile):
      ecode = 0
  
      try:
          with open(infile, 'r') as FH:
              anchore_data = json.loads(FH.read())
  
          ret = anchorecli.clients.apiexternal.import_image(config, anchore_data=anchore_data)
          ecode = anchorecli.cli.utils.get_ecode(ret)
          if ret['success']:
              print(anchorecli.cli.utils.format_output(config, 'image_import', {}, ret['payload']))
          else:
              raise Exception(json.dumps(ret['error'], indent=4))
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_import', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  @image.command(name='get', short_help="Get an image")
  @click.argument('input_image', nargs=1)
  @click.option('--show-history', is_flag=True, help="Show history of images that match the input image, if input image is of the form registry/repo:tag")
  def get(input_image, show_history):
      """
      INPUT_IMAGE: Input image can be in the following formats: Image Digest, ImageID or registry/repo:tag
      """
      ecode = 0
  
      try:
          itype = anchorecli.cli.utils.discover_inputimage_format(config, input_image)
          image = input_image
  
          _logger.debug("discovery from input: %s : %s", str(itype), str(image))
          if itype == 'tag':
              ret = anchorecli.clients.apiexternal.get_image(config, tag=image, history=show_history)
          elif itype == 'imageid':
              ret = anchorecli.clients.apiexternal.get_image(config, image_id=image, history=False)
          elif itype == 'imageDigest':
              ret = anchorecli.clients.apiexternal.get_image(config, imageDigest=image, history=False)
          else:
              ecode = 1
              raise Exception("cannot use input image string: no discovered imageDigest")
  
          if ret:
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret['success']:
                  print(anchorecli.cli.utils.format_output(config, 'image_get', {}, ret['payload']))
              else:
                  raise Exception(json.dumps(ret['error'], indent=4))
          else:
              raise Exception("operation failed with empty response")
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_get', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  @image.command(name='list', short_help="List all images")
  @click.option('--full', is_flag=True, help="Show full row output for each image")
  @click.option('--show-all', is_flag=True, help="Show all images in the system instead of just the latest for a given tag")
  def imagelist(full, show_all):
      ecode = 0
  
      try:
          ret = anchorecli.clients.apiexternal.get_images(config)
          ecode = anchorecli.cli.utils.get_ecode(ret)
          if ret['success']:
              print(anchorecli.cli.utils.format_output(config, 'image_list', {'full':full, 'show_all':show_all}, ret['payload']))
          else:
              raise Exception(json.dumps(ret['error'], indent=4))
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_list', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  @image.command(name='content', short_help="Get contents of image")
  @click.argument('input_image', nargs=1)
  @click.argument('content_type', nargs=1, required=False)
  def query_content(input_image, content_type):
      """
      INPUT_IMAGE: Input image can be in the following formats: Image Digest, ImageID or registry/repo:tag
  
      CONTENT_TYPE: Image content comes in many flavors, and the types are dynamic. If you want to see the content types
      available for an image, run the following command:
          $ anchore-cli image content <input_image>
      """
      ecode = 0
  
      try:
          itype, image, imageDigest = anchorecli.cli.utils.discover_inputimage(config, input_image)
          _logger.debug("discovery from input: %s : %s : %s",  str(itype), str(image), str(imageDigest))
  
          if not imageDigest:
              ecode = 1
              raise Exception("cannot use input image string (no discovered imageDigest)")
          else:
              ret = anchorecli.clients.apiexternal.query_image(config, imageDigest=imageDigest, query_group='content', query_type=content_type)
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret:
                  if ret['success']:
                      output = anchorecli.cli.utils.format_output(config, 'image_content', {'query_type':content_type}, ret['payload'])
                      if not output:
                          raise Exception('There is no image content (%s) to provide for %s' % (content_type, input_image))
                      print(output)
                  else:
                      raise Exception (json.dumps(ret['error'], indent=4))
              else:
                  raise Exception("operation failed with empty response")
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_content', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  @image.command(name='metadata', short_help="Get metadata about an image")
  @click.argument('input_image', nargs=1)
  @click.argument('metadata_type', nargs=1, required=False)
  def query_metadata(input_image, metadata_type):
      """
      INPUT_IMAGE: Input image can be in the following formats: Image Digest, ImageID or registry/repo:tag
  
      METADATA_TYPE: The metadata type can be one of the types returned by running without a type specified
  
      """
      ecode = 0
  
      try:
          itype, image, imageDigest = anchorecli.cli.utils.discover_inputimage(config, input_image)
          _logger.debug("discovery from input: %s : %s : %s", str(itype), str(image), str(imageDigest))
  
          if not imageDigest:
              ecode = 1
              raise Exception("cannot use input image string (no discovered imageDigest)")
          else:
              ret = anchorecli.clients.apiexternal.query_image(config,
                                                               imageDigest=imageDigest,
                                                               query_group='metadata',
                                                               query_type=metadata_type)
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret:
                  if ret['success']:
                      print(anchorecli.cli.utils.format_output(config,
                                                               'image_metadata',
                                                               {'query_type': metadata_type},
                                                               ret['payload']))
                  else:
                      raise Exception(json.dumps(ret['error'], indent=4))
              else:
                  raise Exception("operation failed with empty response")
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_metadata', {}, err))
          if not ecode:
              ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  
  
  @image.command(name='vuln', short_help="Get image vulnerabilities")
  @click.argument('input_image', nargs=1)
  @click.argument('vuln_type', nargs=1, required=False)
  @click.option('--vendor-only', default=True, type=bool, help="Show only vulnerabilities marked by upstream vendor as applicable (default=True)")
  def query_vuln(input_image, vuln_type, vendor_only):
      """
      INPUT_IMAGE: Input image can be in the following formats: Image Digest, ImageID or registry/repo:tag
  
      VULN_TYPE: VULN_TYPE: Vulnerability type can be one of the following types:
  
        - os: CVE/distro vulnerabilities against operating system packages
      """
      ecode = 0
      try:
          itype, image, imageDigest = anchorecli.cli.utils.discover_inputimage(config, input_image)
  
          if not imageDigest:
              ecode = 1
              raise Exception("cannot use input image string (no discovered imageDigest)")
          else:
              ret = anchorecli.clients.apiexternal.query_image(config, imageDigest=imageDigest, query_group='vuln', query_type=vuln_type, vendor_only=vendor_only)
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if ret:
                  if ret['success']:
                      print(anchorecli.cli.utils.format_output(config, 'image_vuln', {'query_type': vuln_type}, ret['payload']))
                  else:
                      if 'analysis_status: analyzing' in ret['error']['message']:
                          ecode = 100
                      elif 'analysis_status: not_analyzed' in ret['error']['message']:
                          ecode = 101
                      raise Exception(json.dumps(ret['error'], indent=4))
              else:
                  raise Exception("operation failed with empty response")
  
      except Exception as err:
          print(anchorecli.cli.utils.format_error_output(config, 'image_vuln', {}, err))
          if not ecode:
              ecode = 2
      anchorecli.cli.utils.doexit(ecode)
  
  
  @image.command(name='del', short_help="Delete an image")
  @click.argument('input_image', required=False)
  @click.option('--force', is_flag=True, help="Force deletion of image by cancelling any subscription/notification settings prior to image delete")
  @click.option('--all', is_flag=True, help="Delete all images")
  def delete(input_image, force, all):
      """
      INPUT_IMAGE: Input image can be in the following formats: Image Digest, ImageID or registry/repo:tag
      """
      ecode = 0
  
      if all:
          try:
              ret = anchorecli.clients.apiexternal.get_images(config)
              ecode = anchorecli.cli.utils.get_ecode(ret)
              if not ret['success']:
                  raise Exception(json.dumps(ret['error'], indent=4))
  
              for image in ret['payload']:
                  if image['imageDigest']:
                      ret = anchorecli.clients.apiexternal.delete_image(config, imageDigest=image['imageDigest'], force=force)
                      if ret['success']:
                          payload = ret.get('payload')
                          if isinstance(payload, dict) and payload.get('status') != 'deleting':  # >= v.0.8.8
                              raise Exception(payload.get('detail'))
  
                          for image_detail in image['image_detail']:
                              fulltag = image_detail.pop('registry', "None") + "/" + image_detail.pop('repo', "None") + ":" + image_detail.pop('tag', "None")
                              print(fulltag)
                      else:
                          raise Exception(json.dumps(ret['error'], indent=4))
  
          except Exception as err:
              print(anchorecli.cli.utils.format_error_output(config, 'image_delete_all', {}, err))
              if not ecode:
                  ecode = 2
      else:
          try:
              if input_image is None:
                  raise Exception("Missing argument INPUT_IMAGE")
  
              itype, image, imageDigest = anchorecli.cli.utils.discover_inputimage(config, input_image)
  
              if imageDigest:
                  ret = anchorecli.clients.apiexternal.delete_image(config, imageDigest=imageDigest, force=force)
                  ecode = anchorecli.cli.utils.get_ecode(ret)
              else:
                  ecode = 1
                  raise Exception("cannot use input image string: no discovered imageDigest")
  
              if ret:
                  if ret['success']:
                      payload = ret.get('payload')
                      if isinstance(payload, dict) and payload.get('status') != 'deleting':  # >= v.0.8.8
                          ecode = 1  # backwards compatibility with pre v0.8.0 error
                          raise Exception(payload.get('detail', 'cannot delete image'))
                      else:
                          print(anchorecli.cli.utils.format_output(config, 'image_delete', {}, ret['payload']))
                  else:
                      raise Exception(json.dumps(ret['error'], indent=4))
              else:
                  raise Exception("operation failed with empty response")
  
          except Exception as err:
              print(anchorecli.cli.utils.format_error_output(config, 'image_delete', {}, err))
              if not ecode:
                  ecode = 2
  
      anchorecli.cli.utils.doexit(ecode)
  