docs: def add_source(source, key=None, fail_invalid=False):
      """Add a package source to this system.
  
      @param source: a URL or sources.list entry, as supported by
      add-apt-repository(1). Examples::
  
          ppa:charmers/example
          deb https://stub:key@private.example.com/ubuntu trusty main
  
      In addition:
          'proposed:' may be used to enable the standard 'proposed'
          pocket for the release.
          'cloud:' may be used to activate official cloud archive pockets,
          such as 'cloud:icehouse'
          'distro' may be used as a noop
  
      Full list of source specifications supported by the function are:
  
      'distro': A NOP; i.e. it has no effect.
      'proposed': the proposed deb spec [2] is wrtten to
        /etc/apt/sources.list/proposed
      'distro-proposed': adds <version>-proposed to the debs [2]
      'ppa:<ppa-name>': add-apt-repository --yes <ppa_name>
      'deb <deb-spec>': add-apt-repository --yes deb <deb-spec>
      'http://....': add-apt-repository --yes http://...
      'cloud-archive:<spec>': add-apt-repository -yes cloud-archive:<spec>
      'cloud:<release>[-staging]': specify a Cloud Archive pocket <release> with
        optional staging version.  If staging is used then the staging PPA [2]
        with be used.  If staging is NOT used then the cloud archive [3] will be
        added, and the 'ubuntu-cloud-keyring' package will be added for the
        current distro.
  
      Otherwise the source is not recognised and this is logged to the juju log.
      However, no error is raised, unless sys_error_on_exit is True.
  
      [1] deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
          where {} is replaced with the derived pocket name.
      [2] deb http://archive.ubuntu.com/ubuntu {}-proposed \
          main universe multiverse restricted
          where {} is replaced with the lsb_release codename (e.g. xenial)
      [3] deb http://ubuntu-cloud.archive.canonical.com/ubuntu <pocket>
          to /etc/apt/sources.list.d/cloud-archive-list
  
      @param key: A key to be added to the system's APT keyring and used
      to verify the signatures on packages. Ideally, this should be an
      ASCII format GPG public key including the block headers. A GPG key
      id may also be used, but be aware that only insecure protocols are
      available to retrieve the actual public key from a public keyserver
      placing your Juju environment at risk. ppa and cloud archive keys
      are securely added automtically, so sould not be provided.
  
      @param fail_invalid: (boolean) if True, then the function raises a
      SourceConfigError is there is no matching installation source.
  
      @raises SourceConfigError() if for cloud:<pocket>, the <pocket> is not a
      valid pocket in CLOUD_ARCHIVE_POCKETS
      """
      _mapping = OrderedDict([
          (r"^distro$", lambda: None),  # This is a NOP
          (r"^(?:proposed|distro-proposed)$", _add_proposed),
          (r"^cloud-archive:(.*)$", _add_apt_repository),
          (r"^((?:deb |http:|https:|ppa:).*)$", _add_apt_repository),
          (r"^cloud:(.*)-(.*)\/staging$", _add_cloud_staging),
          (r"^cloud:(.*)-(.*)$", _add_cloud_distro_check),
          (r"^cloud:(.*)$", _add_cloud_pocket),
          (r"^snap:.*-(.*)-(.*)$", _add_cloud_distro_check),
      ])
      if source is None:
          source = ''
      for r, fn in six.iteritems(_mapping):
          m = re.match(r, source)
          if m:
              # call the assoicated function with the captured groups
              # raises SourceConfigError on error.
              fn(*m.groups())
              if key:
                  try:
                      import_key(key)
                  except GPGKeyError as e:
                      raise SourceConfigError(str(e))
              break
      else:
          # nothing matched.  log an error and maybe sys.exit
          err = "Unknown source: {!r}".format(source)
          log(err)
          if fail_invalid:
              raise SourceConfigError(err)