docs: def create_upload_specifications(ctx_cli_options, config):
      # type: (dict, dict) -> List[blobxfer.models.upload.Specification]
      """Create a list of Upload Specification objects from configuration
      :param dict ctx_cli_options: cli options
      :param dict config: config dict
      :rtype: list
      :return: list of Upload Specification objects
      """
      cli_conf = ctx_cli_options[ctx_cli_options['_action']]
      cli_options = cli_conf['options']
      specs = []
      for conf in config['upload']:
          if 'options' in conf:
              conf_options = conf['options']
          else:
              conf_options = {}
          # create upload options
          mode = _merge_setting(
              cli_options, conf_options, 'mode', default='auto').lower()
          if mode == 'auto':
              mode = blobxfer.models.azure.StorageModes.Auto
          elif mode == 'append':
              mode = blobxfer.models.azure.StorageModes.Append
          elif mode == 'block':
              mode = blobxfer.models.azure.StorageModes.Block
          elif mode == 'file':
              mode = blobxfer.models.azure.StorageModes.File
          elif mode == 'page':
              mode = blobxfer.models.azure.StorageModes.Page
          else:
              raise ValueError('unknown mode: {}'.format(mode))
          # load RSA public key PEM if specified
          rpk = _merge_setting(cli_options, conf_options, 'rsa_public_key')
          if blobxfer.util.is_not_empty(rpk):
              rpk = blobxfer.operations.crypto.load_rsa_public_key_file(rpk)
          if rpk is None:
              # load RSA private key PEM file if specified
              rpk = _merge_setting(
                  cli_options, conf_options, 'rsa_private_key')
              if blobxfer.util.is_not_empty(rpk):
                  rpkp = _merge_setting(
                      cli_options, conf_options, 'rsa_private_key_passphrase')
                  rpk = blobxfer.operations.crypto.load_rsa_private_key_file(
                      rpk, rpkp)
                  rpk = rpk.public_key()
              else:
                  rpk = None
          # create local source paths
          lsp = blobxfer.models.upload.LocalSourcePath()
          lsp.add_paths(conf['source'])
          incl = _merge_setting(cli_conf, conf, 'include', default=None)
          if blobxfer.util.is_not_empty(incl):
              lsp.add_includes(incl)
          excl = _merge_setting(cli_conf, conf, 'exclude', default=None)
          if blobxfer.util.is_not_empty(excl):
              lsp.add_excludes(excl)
          # create specification
          conf_sfp = conf_options.get('store_file_properties', {})
          cli_sfp = cli_options['store_file_properties']
          conf_vio = conf_options.get('vectored_io', {})
          cli_vio = cli_options['vectored_io']
          conf_sod = conf_options.get('skip_on', {})
          cli_sod = cli_options['skip_on']
          us = blobxfer.models.upload.Specification(
              upload_options=blobxfer.models.options.Upload(
                  access_tier=_merge_setting(
                      cli_options, conf_options, 'access_tier', default=None),
                  chunk_size_bytes=_merge_setting(
                      cli_options, conf_options, 'chunk_size_bytes',
                      default=0),
                  delete_extraneous_destination=_merge_setting(
                      cli_options, conf_options,
                      'delete_extraneous_destination', default=False),
                  mode=mode,
                  one_shot_bytes=_merge_setting(
                      cli_options, conf_options, 'one_shot_bytes', default=0),
                  overwrite=_merge_setting(
                      cli_options, conf_options, 'overwrite', default=True),
                  recursive=_merge_setting(
                      cli_options, conf_options, 'recursive', default=True),
                  rename=_merge_setting(
                      cli_options, conf_options, 'rename', default=False),
                  rsa_public_key=rpk,
                  store_file_properties=blobxfer.models.options.FileProperties(
                      attributes=_merge_setting(
                          cli_sfp, conf_sfp, 'attributes', default=False),
                      cache_control=_merge_setting(
                          cli_sfp, conf_sfp, 'cache_control', default=None),
                      lmt=None,
                      md5=_merge_setting(
                          cli_sfp, conf_sfp, 'md5', default=False),
                  ),
                  stdin_as_page_blob_size=_merge_setting(
                      cli_options, conf_options, 'stdin_as_page_blob_size',
                      default=0),
                  strip_components=_merge_setting(
                      cli_options, conf_options, 'strip_components',
                      default=0),
                  vectored_io=blobxfer.models.options.VectoredIo(
                      stripe_chunk_size_bytes=_merge_setting(
                          cli_vio, conf_vio, 'stripe_chunk_size_bytes',
                          default=1073741824),
                      distribution_mode=blobxfer.
                      models.upload.VectoredIoDistributionMode(
                          _merge_setting(
                              cli_vio, conf_vio, 'distribution_mode',
                              default='disabled').lower()),
                  ),
              ),
              skip_on_options=blobxfer.models.options.SkipOn(
                  filesize_match=_merge_setting(
                      cli_sod, conf_sod, 'filesize_match', default=False),
                  lmt_ge=_merge_setting(
                      cli_sod, conf_sod, 'lmt_ge', default=False),
                  md5_match=_merge_setting(
                      cli_sod, conf_sod, 'md5_match', default=False),
              ),
              local_source_path=lsp,
          )
          # create remote destination paths
          for dst in conf['destination']:
              if len(dst) != 1:
                  raise RuntimeError(
                      'invalid number of destination pairs specified per entry')
              sa = next(iter(dst))
              adp = blobxfer.operations.azure.DestinationPath()
              adp.add_path_with_storage_account(dst[sa], sa)
              us.add_azure_destination_path(adp)
          # append spec to list
          specs.append(us)
      return specs