Exemplo n.º 1
0
def write_file(filename,
               data,
               mode='w',
               continue_on_error=False,
               display_errors=True):
    """Writes data to a file.

  Args:
    filename: String, the path of the file to write to disk.
    data: Serializable data to write to the file.
    mode: String, the mode in which to open the file and write to it.
    continue_on_error: Boolean, If True, suppresses any IO errors and returns to
      the caller without any externalities.
    display_errors: Boolean, If True, prints error messages when errors are
      encountered and continue_on_error is True.

  Returns:
    Boolean, True if the write operation succeeded, or False if not.
  """
    try:
        with _open_file(filename, mode) as f:
            f.write(data)
        return True

    except IOError as e:
        if continue_on_error:
            if display_errors:
                display.print_error(e)
            return False
        else:
            controlflow.system_error_exit(6, e)
Exemplo n.º 2
0
def getHoldInfo():
    v = buildGAPIObject()
    hold = sys.argv[3]
    matterId = None
    i = 4
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'matter':
            matterId = getMatterItem(v, sys.argv[i+1])
            holdId = convertHoldNameToID(v, hold, matterId)
            i += 2
        else:
            controlflow.invalid_argument_exit(myarg, "gam info hold")
    if not matterId:
        controlflow.system_error_exit(
            3, 'you must specify a matter for the hold.')
    results = gapi.call(v.matters().holds(), 'get',
                        matterId=matterId, holdId=holdId)
    cd = __main__.buildGAPIObject('directory')
    if 'accounts' in results:
        account_type = 'group' if results['corpus'] == 'GROUPS' else 'user'
        for i in range(0, len(results['accounts'])):
            uid = f'uid:{results["accounts"][i]["accountId"]}'
            acct_email = __main__.convertUIDtoEmailAddress(
                uid, cd, [account_type])
            results['accounts'][i]['email'] = acct_email
    if 'orgUnit' in results:
        results['orgUnit']['orgUnitPath'] = __main__.doGetOrgInfo(
            results['orgUnit']['orgUnitId'], return_attrib='orgUnitPath')
    display.print_json(results)
Exemplo n.º 3
0
def doUpdateCustomer():
    cd = gapi.directory.buildGAPIObject()
    body = {}
    i = 3
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg in ADDRESS_FIELDS_ARGUMENT_MAP:
            body.setdefault('postalAddress', {})
            arg = ADDRESS_FIELDS_ARGUMENT_MAP[myarg]
            body['postalAddress'][arg] = sys.argv[i + 1]
            i += 2
        elif myarg in ['adminsecondaryemail', 'alternateemail']:
            body['alternateEmail'] = sys.argv[i + 1]
            i += 2
        elif myarg in ['phone', 'phonenumber']:
            body['phoneNumber'] = sys.argv[i + 1]
            i += 2
        elif myarg == 'language':
            body['language'] = sys.argv[i + 1]
            i += 2
        else:
            controlflow.invalid_argument_exit(myarg, "gam update customer")
    if not body:
        controlflow.system_error_exit(
            2, 'no arguments specified for "gam '
            'update customer"')
    gapi.call(cd.customers(),
              'patch',
              customerKey=GC_Values[GC_CUSTOMER_ID],
              body=body)
    print('Updated customer')
Exemplo n.º 4
0
def updateMatter(action=None):
    v = buildGAPIObject()
    matterId = getMatterItem(v, sys.argv[3])
    body = {}
    action_kwargs = {'body': {}}
    add_collaborators = []
    remove_collaborators = []
    cd = None
    i = 4
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'action':
            action = sys.argv[i+1].lower()
            if action not in VAULT_MATTER_ACTIONS:
                controlflow.system_error_exit(3, f'allowed actions are ' \
                    f'{", ".join(VAULT_MATTER_ACTIONS)}, got {action}')
            i += 2
        elif myarg == 'name':
            body['name'] = sys.argv[i+1]
            i += 2
        elif myarg == 'description':
            body['description'] = sys.argv[i+1]
            i += 2
        elif myarg in ['addcollaborator', 'addcollaborators']:
            if not cd:
                cd = __main__.buildGAPIObject('directory')
            add_collaborators.extend(validateCollaborators(sys.argv[i+1], cd))
            i += 2
        elif myarg in ['removecollaborator', 'removecollaborators']:
            if not cd:
                cd = __main__.buildGAPIObject('directory')
            remove_collaborators.extend(
                validateCollaborators(sys.argv[i+1], cd))
            i += 2
        else:
            controlflow.invalid_argument_exit(sys.argv[i], "gam update matter")
    if action == 'delete':
        action_kwargs = {}
    if body:
        print(f'Updating matter {sys.argv[3]}...')
        if 'name' not in body or 'description' not in body:
            # bah, API requires name/description to be sent
            # on update even when it's not changing
            result = gapi.call(v.matters(), 'get',
                               matterId=matterId, view='BASIC')
            body.setdefault('name', result['name'])
            body.setdefault('description', result.get('description'))
        gapi.call(v.matters(), 'update', body=body, matterId=matterId)
    if action:
        print(f'Performing {action} on matter {sys.argv[3]}')
        gapi.call(v.matters(), action, matterId=matterId, **action_kwargs)
    for collaborator in add_collaborators:
        print(f' adding collaborator {collaborator["email"]}')
        body = {'matterPermission': {'role': 'COLLABORATOR',
                                     'accountId': collaborator['id']}}
        gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body)
    for collaborator in remove_collaborators:
        print(f' removing collaborator {collaborator["email"]}')
        gapi.call(v.matters(), 'removePermissions', matterId=matterId,
                  body={'accountId': collaborator['id']})
Exemplo n.º 5
0
def _adjust_date(errMsg):
    match_date = re.match('Data for dates later than (.*) is not yet '
                          'available. Please check back later', errMsg)
    if not match_date:
        match_date = re.match('Start date can not be later than (.*)', errMsg)
    if not match_date:
        controlflow.system_error_exit(4, errMsg)
    return str(match_date.group(1))
Exemplo n.º 6
0
def get_date_zero_time_or_full_time(time_string):
    time_string = time_string.strip()
    if time_string:
        if YYYYMMDD_PATTERN.match(time_string):
            return get_yyyymmdd(time_string) + 'T00:00:00.000Z'
        return get_time_or_delta_from_now(time_string)
    controlflow.system_error_exit(
        2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>')
Exemplo n.º 7
0
def validateCollaborators(collaboratorList, cd):
    collaborators = []
    for collaborator in collaboratorList.split(','):
        collaborator_id = __main__.convertEmailAddressToUID(collaborator, cd)
        if not collaborator_id:
            controlflow.system_error_exit(4, f'failed to get a UID for '
                                             f'{collaborator}. Please make '
                                             f'sure this is a real user.')
        collaborators.append({'email': collaborator, 'id': collaborator_id})
    return collaborators
Exemplo n.º 8
0
def md5_matches_file(local_file, expected_md5, exitOnError):
    f = fileutils.open_file(local_file, 'rb')
    hash_md5 = md5()
    for chunk in iter(lambda: f.read(4096), b""):
        hash_md5.update(chunk)
    actual_hash = hash_md5.hexdigest()
    if exitOnError and actual_hash != expected_md5:
        controlflow.system_error_exit(
            6, f'actual hash was {actual_hash}. Exiting on corrupt file.')
    return actual_hash == expected_md5
Exemplo n.º 9
0
def open_file(filename,
              mode='r',
              encoding=None,
              newline=None,
              strip_utf_bom=False):
    """Opens a file.

  Args:
    filename: String, the name of the file to open, or '-' to use stdin/stdout,
      to read/write, depending on the mode param, respectively.
    mode: String, the common file mode to open the file with. Default is read.
    encoding: String, the name of the encoding used to decode or encode the
      file. This should only be used in text mode.
    newline: See param description in
        https://docs.python.org/3.7/library/functions.html#open
    strip_utf_bom: Boolean, True if the file being opened should seek past the
      UTF Byte Order Mark before being returned.
        See more: https://en.wikipedia.org/wiki/UTF-8#Byte_order_mark

  Returns:
    The opened file.
  """
    try:
        if filename == '-':
            # Read from stdin, rather than a file
            if 'r' in mode:
                return io.StringIO(str(sys.stdin.read()))
            return sys.stdout

        # Open a file on disk
        f = _open_file(filename, mode, newline=newline, encoding=encoding)
        if strip_utf_bom:
            utf_bom = u'\ufeff'
            has_bom = False

            if 'b' in mode:
                has_bom = f.read(3).decode('UTF-8') == utf_bom
            elif f.encoding and not f.encoding.lower().startswith('utf'):
                # Convert UTF BOM into ISO-8859-1 via Bytes
                utf8_bom_bytes = utf_bom.encode('UTF-8')
                iso_8859_1_bom = utf8_bom_bytes.decode('iso-8859-1').encode(
                    'iso-8859-1')
                has_bom = f.read(3).encode('iso-8859-1',
                                           'replace') == iso_8859_1_bom
            else:
                has_bom = f.read(1) == utf_bom

            if not has_bom:
                f.seek(0)

        return f

    except IOError as e:
        controlflow.system_error_exit(6, e)
Exemplo n.º 10
0
def convertHoldNameToID(v, nameOrID, matterId):
    nameOrID = nameOrID.lower()
    cg = UID_PATTERN.match(nameOrID)
    if cg:
        return cg.group(1)
    fields = 'holds(holdId,name),nextPageToken'
    holds = gapi.get_all_pages(v.matters().holds(
    ), 'list', 'holds', matterId=matterId, fields=fields)
    for hold in holds:
        if hold['name'].lower() == nameOrID:
            return hold['holdId']
    controlflow.system_error_exit(4, f'could not find hold name {nameOrID} '
                                     f'in matter {matterId}')
Exemplo n.º 11
0
def convertExportNameToID(v, nameOrID, matterId):
    nameOrID = nameOrID.lower()
    cg = UID_PATTERN.match(nameOrID)
    if cg:
        return cg.group(1)
    fields = 'exports(id,name),nextPageToken'
    exports = gapi.get_all_pages(v.matters().exports(
    ), 'list', 'exports', matterId=matterId, fields=fields)
    for export in exports:
        if export['name'].lower() == nameOrID:
            return export['id']
    controlflow.system_error_exit(4, f'could not find export name {nameOrID} '
                                     f'in matter {matterId}')
Exemplo n.º 12
0
def get_time_or_delta_from_now(time_string):
    """Get an ISO 8601 time or a positive/negative delta applied to now.
  Args:
    time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h')
  Returns:
    string: iso8601 formatted datetime in UTC.
  """
    time_string = time_string.strip().upper()
    if time_string:
        if time_string[0] not in ['+', '-']:
            return time_string
        return (datetime.datetime.utcnow() +
                get_delta_time(time_string)).isoformat() + 'Z'
    controlflow.system_error_exit(
        2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>')
Exemplo n.º 13
0
def deleteHold():
    v = buildGAPIObject()
    hold = sys.argv[3]
    matterId = None
    i = 4
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'matter':
            matterId = getMatterItem(v, sys.argv[i+1])
            holdId = convertHoldNameToID(v, hold, matterId)
            i += 2
        else:
            controlflow.invalid_argument_exit(myarg, "gam delete hold")
    if not matterId:
        controlflow.system_error_exit(
            3, 'you must specify a matter for the hold.')
    print(f'Deleting hold {hold} / {holdId}')
    gapi.call(v.matters().holds(), 'delete', matterId=matterId, holdId=holdId)
Exemplo n.º 14
0
def read_file(filename,
              mode='r',
              encoding=None,
              newline=None,
              continue_on_error=False,
              display_errors=True):
    """Reads a file from disk.

  Args:
    filename: String, the path of the file to open from disk, or "-" to read
      from stdin.
    mode: String, the mode in which to open the file.
    encoding: String, the name of the encoding used to decode or encode the
      file. This should only be used in text mode.
    newline: See param description in
        https://docs.python.org/3.7/library/functions.html#open
    continue_on_error: Boolean, If True, suppresses any IO errors and returns to
      the caller without any externalities.
    display_errors: Boolean, If True, prints error messages when errors are
      encountered and continue_on_error is True.

  Returns:
    The contents of the file, or stdin if filename == "-". Returns None if
    an error is encountered and continue_on_errors is True.
  """
    try:
        if filename == '-':
            # Read from stdin, rather than a file.
            return str(sys.stdin.read())

        with _open_file(filename, mode, newline=newline,
                        encoding=encoding) as f:
            return f.read()

    except IOError as e:
        if continue_on_error:
            if display_errors:
                display.print_warning(e)
            return None
        controlflow.system_error_exit(6, e)
    except (LookupError, UnicodeDecodeError, UnicodeError) as e:
        controlflow.system_error_exit(2, str(e))
Exemplo n.º 15
0
def moveOrDeleteEvent(moveOrDelete):
    calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2])
    if not cal:
        return
    sendUpdates = None
    doit = False
    kwargs = {}
    i = 4
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg in ['notifyattendees', 'sendnotifications', 'sendupdates']:
            sendUpdates, i = getSendUpdates(myarg, i, cal)
        elif myarg in ['id', 'eventid']:
            eventId = sys.argv[i + 1]
            i += 2
        elif myarg in ['query', 'eventquery']:
            controlflow.system_error_exit(
                2, f'query is no longer supported for {moveOrDelete}event. ' \
                   f'Use "gam calendar <email> printevents query <query> | ' \
                   f'gam csv - gam {moveOrDelete}event id ~id" instead.')
        elif myarg == 'doit':
            doit = True
            i += 1
        elif moveOrDelete == 'move' and myarg == 'destination':
            kwargs['destination'] = sys.argv[i + 1]
            i += 2
        else:
            controlflow.invalid_argument_exit(
                sys.argv[i], f"gam calendar <email> {moveOrDelete}event")
    if doit:
        print(f' going to {moveOrDelete} eventId {eventId}')
        gapi.call(cal.events(),
                  moveOrDelete,
                  calendarId=calendarId,
                  eventId=eventId,
                  sendUpdates=sendUpdates,
                  **kwargs)
    else:
        print(
            f' would {moveOrDelete} eventId {eventId}. Add doit to command ' \
            f'to actually {moveOrDelete} event')
Exemplo n.º 16
0
def handle_oauth_token_error(e, soft_errors):
    """On a token error, exits the application and writes a message to stderr.

  Args:
    e: google.auth.exceptions.RefreshError, The error to handle.
    soft_errors: Boolean, if True, suppresses any applicable errors and instead
      returns to the caller.
  """
    token_error = str(e).replace('.', '')
    if token_error in errors.OAUTH2_TOKEN_ERRORS or e.startswith(
            'Invalid response'):
        if soft_errors:
            return
        if not GM_Globals[GM_CURRENT_API_USER]:
            display.print_error(
                MESSAGE_API_ACCESS_DENIED.format(
                    GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID],
                    ','.join(GM_Globals[GM_CURRENT_API_SCOPES])))
            controlflow.system_error_exit(12, MESSAGE_API_ACCESS_CONFIG)
        else:
            controlflow.system_error_exit(
                19,
                MESSAGE_SERVICE_NOT_APPLICABLE.format(
                    GM_Globals[GM_CURRENT_API_USER]))
    controlflow.system_error_exit(18, f'Authentication Token Error - {str(e)}')
Exemplo n.º 17
0
def get_yyyymmdd(argstr,
                 minLen=1,
                 returnTimeStamp=False,
                 returnDateTime=False):
    argstr = argstr.strip()
    if argstr:
        if argstr[0] in ['+', '-']:
            today = datetime.date.today()
            argstr = (datetime.datetime(today.year, today.month, today.day) +
                      get_delta_date(argstr)).strftime(YYYYMMDD_FORMAT)
        try:
            dateTime = datetime.datetime.strptime(argstr, YYYYMMDD_FORMAT)
            if returnTimeStamp:
                return time.mktime(dateTime.timetuple()) * 1000
            if returnDateTime:
                return dateTime
            return argstr
        except ValueError:
            controlflow.system_error_exit(
                2, f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>; got {argstr}')
    elif minLen == 0:
        return ''
    controlflow.system_error_exit(2,
                                  f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>')
Exemplo n.º 18
0
def getBuildingByNameOrId(cd, which_building, minLen=1):
    if not which_building or \
       (minLen == 0 and which_building in ['id:', 'uid:']):
        if minLen == 0:
            return ''
        controlflow.system_error_exit(3, 'Building id/name is empty')
    cg = UID_PATTERN.match(which_building)
    if cg:
        return cg.group(1)
    if GM_Globals[GM_MAP_BUILDING_NAME_TO_ID] is None:
        _makeBuildingIdNameMap(cd)
    # Exact name match, return ID
    if which_building in GM_Globals[GM_MAP_BUILDING_NAME_TO_ID]:
        return GM_Globals[GM_MAP_BUILDING_NAME_TO_ID][which_building]
    # No exact name match, check for case insensitive name matches
    which_building_lower = which_building.lower()
    ci_matches = []
    for buildingName, buildingId in GM_Globals[
            GM_MAP_BUILDING_NAME_TO_ID].items():
        if buildingName.lower() == which_building_lower:
            ci_matches.append({
                'buildingName': buildingName,
                'buildingId': buildingId
            })
    # One match, return ID
    if len(ci_matches) == 1:
        return ci_matches[0]['buildingId']
    # No or multiple name matches, try ID
    # Exact ID match, return ID
    if which_building in GM_Globals[GM_MAP_BUILDING_ID_TO_NAME]:
        return which_building
    # No exact ID match, check for case insensitive id match
    for buildingId in GM_Globals[GM_MAP_BUILDING_ID_TO_NAME]:
        # Match, return ID
        if buildingId.lower() == which_building_lower:
            return buildingId
    # Multiple name  matches
    if len(ci_matches) > 1:
        message = 'Multiple buildings with same name:\n'
        for building in ci_matches:
            message += f'  Name:{building["buildingName"]}  ' \
                       f'id:{building["buildingId"]}\n'
        message += '\nPlease specify building name by exact case or by id.'
        controlflow.system_error_exit(3, message)
    # No matches
    else:
        controlflow.system_error_exit(3, f'No such building {which_building}')
Exemplo n.º 19
0
def get_string(i, item, optional=False, minLen=1, maxLen=None):
    if i < len(sys.argv):
        argstr = sys.argv[i]
        if argstr:
            if (len(argstr) >= minLen) and ((maxLen is None) or
                                            (len(argstr) <= maxLen)):
                return argstr
            controlflow.system_error_exit(
                2,
                f'expected <{integerLimits(minLen, maxLen, "string length")} for {item}>'
            )
        if optional or (minLen == 0):
            return ''
        controlflow.system_error_exit(2, f'expected a Non-empty <{item}>')
    elif optional:
        return ''
    controlflow.system_error_exit(2, f'expected a <{item}>')
Exemplo n.º 20
0
def write_csv_file(csvRows, titles, list_type, todrive):
    def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate):
        if not rowDate or not isinstance(rowDate, str):
            return False
        try:
            rowTime = dateutil.parser.parse(rowDate, ignoretz=True)
            if dateMode:
                rowDate = datetime.datetime(rowTime.year, rowTime.month,
                                            rowTime.day).isoformat() + 'Z'
        except ValueError:
            rowDate = NEVER_TIME
        if op == '<':
            return rowDate < filterDate
        if op == '<=':
            return rowDate <= filterDate
        if op == '>':
            return rowDate > filterDate
        if op == '>=':
            return rowDate >= filterDate
        if op == '!=':
            return rowDate != filterDate
        return rowDate == filterDate

    def rowCountFilterMatch(rowCount, op, filterCount):
        if isinstance(rowCount, str):
            if not rowCount.isdigit():
                return False
            rowCount = int(rowCount)
        elif not isinstance(rowCount, int):
            return False
        if op == '<':
            return rowCount < filterCount
        if op == '<=':
            return rowCount <= filterCount
        if op == '>':
            return rowCount > filterCount
        if op == '>=':
            return rowCount >= filterCount
        if op == '!=':
            return rowCount != filterCount
        return rowCount == filterCount

    def rowBooleanFilterMatch(rowBoolean, filterBoolean):
        if not isinstance(rowBoolean, bool):
            return False
        return rowBoolean == filterBoolean

    def headerFilterMatch(title):
        for filterStr in GC_Values[GC_CSV_HEADER_FILTER]:
            if filterStr.match(title):
                return True
        return False

    if GC_Values[GC_CSV_ROW_FILTER]:
        for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
            if column not in titles:
                sys.stderr.write(
                    f'WARNING: Row filter column "{column}" is not in output columns\n'
                )
                continue
            if filterVal[0] == 'regex':
                csvRows = [
                    row for row in csvRows
                    if filterVal[1].search(str(row.get(column, '')))
                ]
            elif filterVal[0] == 'notregex':
                csvRows = [
                    row for row in csvRows
                    if not filterVal[1].search(str(row.get(column, '')))
                ]
            elif filterVal[0] in ['date', 'time']:
                csvRows = [
                    row for row in csvRows if rowDateTimeFilterMatch(
                        filterVal[0] == 'date', row.get(column, ''),
                        filterVal[1], filterVal[2])
                ]
            elif filterVal[0] == 'count':
                csvRows = [
                    row for row in csvRows if rowCountFilterMatch(
                        row.get(column, 0), filterVal[1], filterVal[2])
                ]
            else:  #boolean
                csvRows = [
                    row for row in csvRows if rowBooleanFilterMatch(
                        row.get(column, False), filterVal[1])
                ]
    if GC_Values[GC_CSV_HEADER_FILTER]:
        titles = [t for t in titles if headerFilterMatch(t)]
        if not titles:
            controlflow.system_error_exit(
                3, 'No columns selected with GAM_CSV_HEADER_FILTER\n')
            return
    csv.register_dialect('nixstdout', lineterminator='\n')
    if todrive:
        write_to = io.StringIO()
    else:
        write_to = sys.stdout
    writer = csv.DictWriter(write_to,
                            fieldnames=titles,
                            dialect='nixstdout',
                            extrasaction='ignore',
                            quoting=csv.QUOTE_MINIMAL)
    try:
        writer.writerow(dict((item, item) for item in writer.fieldnames))
        writer.writerows(csvRows)
    except IOError as e:
        controlflow.system_error_exit(6, e)
    if todrive:
        admin_email = __main__._getValueFromOAuth('email')
        _, drive = __main__.buildDrive3GAPIObject(admin_email)
        if not drive:
            print(
                f'''\nGAM is not authorized to create Drive files. Please run:
gam user {admin_email} check serviceaccount
and follow recommend steps to authorize GAM for Drive access.''')
            sys.exit(5)
        result = gapi.call(drive.about(), 'get', fields='maxImportSizes')
        columns = len(titles)
        rows = len(csvRows)
        cell_count = rows * columns
        data_size = len(write_to.getvalue())
        max_sheet_bytes = int(
            result['maxImportSizes'][MIMETYPE_GA_SPREADSHEET])
        if cell_count > MAX_GOOGLE_SHEET_CELLS or data_size > max_sheet_bytes:
            print(
                f'{WARNING_PREFIX}{MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET}'
            )
            mimeType = 'text/csv'
        else:
            mimeType = MIMETYPE_GA_SPREADSHEET
        body = {
            'description': QuotedArgumentList(sys.argv),
            'name': f'{GC_Values[GC_DOMAIN]} - {list_type}',
            'mimeType': mimeType
        }
        result = gapi.call(drive.files(),
                           'create',
                           fields='webViewLink',
                           body=body,
                           media_body=googleapiclient.http.MediaInMemoryUpload(
                               write_to.getvalue().encode(),
                               mimetype='text/csv'))
        file_url = result['webViewLink']
        if GC_Values[GC_NO_BROWSER]:
            msg_txt = f'Drive file uploaded to:\n {file_url}'
            msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}'
            __main__.send_email(msg_subj, msg_txt)
            print(msg_txt)
        else:
            webbrowser.open(file_url)
Exemplo n.º 21
0
def get_gapi_error_detail(e,
                          soft_errors=False,
                          silent_errors=False,
                          retry_on_http_error=False):
    """Extracts error detail from a non-200 GAPI Response.

  Args:
    e: googleapiclient.HttpError, The HTTP Error received.
    soft_errors: Boolean, If true, causes error messages to be surpressed,
      rather than sending them to stderr.
    silent_errors: Boolean, If true, suppresses and ignores any errors from
      being displayed
    retry_on_http_error: Boolean, If true, will return -1 as the HTTP Response
      code, indicating that the request can be retried. TODO: Remove this param,
        as it seems to be outside the scope of this method.

  Returns:
    A tuple containing the HTTP Response code, GAPI error reason, and error
        message.
  """
    try:
        error = json.loads(e.content.decode(UTF8))
    except ValueError:
        error_content = e.content.decode(UTF8) if isinstance(
            e.content, bytes) else e.content
        if (e.resp['status']
                == '503') and (error_content
                               == 'Quota exceeded for the current request'):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '403') and (error_content.startswith(
                'Request rate higher than configured')):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '403') and ('Invalid domain.'
                                            in error_content):
            error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value,
                                            'Domain not found')
        elif (e.resp['status'] == '400') and ('InvalidSsoSigningKey'
                                              in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'InvalidSsoSigningKey')
        elif (e.resp['status'] == '400') and ('UnknownError' in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'UnknownError')
        elif retry_on_http_error:
            return (-1, None, None)
        elif soft_errors:
            if not silent_errors:
                display.print_error(error_content)
            return (0, None, None)
        else:
            controlflow.system_error_exit(5, error_content)
        # END: ValueError catch

    if 'error' in error:
        http_status = error['error']['code']
        try:
            message = error['error']['errors'][0]['message']
        except KeyError:
            message = error['error']['message']
    else:
        if 'error_description' in error:
            if error['error_description'] == 'Invalid Value':
                message = error['error_description']
                http_status = 400
                error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                                message)
            else:
                controlflow.system_error_exit(4, str(error))
        else:
            controlflow.system_error_exit(4, str(error))

    # Extract the error reason
    try:
        reason = error['error']['errors'][0]['reason']
        if reason == 'notFound':
            if 'userKey' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'groupKey' in message:
                reason = ErrorReason.GROUP_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
            elif 'Domain not found' in message:
                reason = ErrorReason.DOMAIN_NOT_FOUND.value
            elif 'Resource Not Found' in message:
                reason = ErrorReason.RESOURCE_NOT_FOUND.value
        elif reason == 'invalid':
            if 'userId' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.INVALID_MEMBER.value
        elif reason == 'failedPrecondition':
            if 'Bad Request' in message:
                reason = ErrorReason.BAD_REQUEST.value
            elif 'Mail service not enabled' in message:
                reason = ErrorReason.SERVICE_NOT_AVAILABLE.value
        elif reason == 'required':
            if 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
        elif reason == 'conditionNotMet':
            if 'Cyclic memberships not allowed' in message:
                reason = ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value
    except KeyError:
        reason = '{0}'.format(http_status)
    return (http_status, reason, message)
Exemplo n.º 22
0
def createHold():
    v = buildGAPIObject()
    allowed_corpuses = gapi.get_enum_values_minus_unspecified(
        v._rootDesc['schemas']['Hold']['properties']['corpus']['enum'])
    body = {'query': {}}
    i = 3
    query = None
    start_time = None
    end_time = None
    matterId = None
    accounts = []
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'name':
            body['name'] = sys.argv[i+1]
            i += 2
        elif myarg == 'query':
            query = sys.argv[i+1]
            i += 2
        elif myarg == 'corpus':
            body['corpus'] = sys.argv[i+1].upper()
            if body['corpus'] not in allowed_corpuses:
                controlflow.expected_argument_exit(
                    "corpus", ", ".join(allowed_corpuses), sys.argv[i+1])
            i += 2
        elif myarg in ['accounts', 'users', 'groups']:
            accounts = sys.argv[i+1].split(',')
            i += 2
        elif myarg in ['orgunit', 'ou']:
            body['orgUnit'] = {
                'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
            i += 2
        elif myarg in ['start', 'starttime']:
            start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
            i += 2
        elif myarg in ['end', 'endtime']:
            end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
            i += 2
        elif myarg == 'matter':
            matterId = getMatterItem(v, sys.argv[i+1])
            i += 2
        else:
            controlflow.invalid_argument_exit(sys.argv[i], "gam create hold")
    if not matterId:
        controlflow.system_error_exit(
            3, 'you must specify a matter for the new hold.')
    if not body.get('name'):
        controlflow.system_error_exit(
            3, 'you must specify a name for the new hold.')
    if not body.get('corpus'):
        controlflow.system_error_exit(3, f'you must specify a corpus for ' \
          f'the new hold. Choose one of {", ".join(allowed_corpuses)}')
    if body['corpus'] == 'HANGOUTS_CHAT':
        query_type = 'hangoutsChatQuery'
    else:
        query_type = f'{body["corpus"].lower()}Query'
    body['query'][query_type] = {}
    if body['corpus'] == 'DRIVE':
        if query:
            try:
                body['query'][query_type] = json.loads(query)
            except ValueError as e:
                controlflow.system_error_exit(3, f'{str(e)}, query: {query}')
    elif body['corpus'] in ['GROUPS', 'MAIL']:
        if query:
            body['query'][query_type] = {'terms': query}
        if start_time:
            body['query'][query_type]['startTime'] = start_time
        if end_time:
            body['query'][query_type]['endTime'] = end_time
    if accounts:
        body['accounts'] = []
        cd = __main__.buildGAPIObject('directory')
        account_type = 'group' if body['corpus'] == 'GROUPS' else 'user'
        for account in accounts:
            body['accounts'].append(
                {'accountId': __main__.convertEmailAddressToUID(account,
                                                                cd,
                                                                account_type)}
            )
    holdId = gapi.call(v.matters().holds(), 'create',
                       matterId=matterId, body=body, fields='holdId')
    print(f'Created hold {holdId["holdId"]}')
Exemplo n.º 23
0
def call(service,
         function,
         silent_errors=False,
         soft_errors=False,
         throw_reasons=None,
         retry_reasons=None,
         **kwargs):
    """Executes a single request on a Google service function.

  Args:
    service: A Google service object for the desired API.
    function: String, The name of a service request method to execute.
    silent_errors: Bool, If True, error messages are suppressed when
      encountered.
    soft_errors: Bool, If True, writes non-fatal errors to stderr.
    throw_reasons: A list of Google HTTP error reason strings indicating the
      errors generated by this request should be re-thrown. All other HTTP
      errors are consumed.
    retry_reasons: A list of Google HTTP error reason strings indicating which
      error should be retried, using exponential backoff techniques, when the
      error reason is encountered.
    **kwargs: Additional params to pass to the request method.

  Returns:
    A response object for the corresponding Google API call.
  """
    if throw_reasons is None:
        throw_reasons = []
    if retry_reasons is None:
        retry_reasons = []

    method = getattr(service, function)
    retries = 10
    parameters = dict(
        list(kwargs.items()) + list(GM_Globals[GM_EXTRA_ARGS_DICT].items()))
    for n in range(1, retries + 1):
        try:
            return method(**parameters).execute()
        except googleapiclient.errors.HttpError as e:
            http_status, reason, message = errors.get_gapi_error_detail(
                e,
                soft_errors=soft_errors,
                silent_errors=silent_errors,
                retry_on_http_error=n < 3)
            if http_status == -1:
                # The error detail indicated that we should retry this request
                # We'll refresh credentials and make another pass
                service._http.request.credentials.refresh(
                    transport.create_http())
                continue
            if http_status == 0:
                return None

            is_known_error_reason = reason in [
                r.value for r in errors.ErrorReason
            ]
            if is_known_error_reason and errors.ErrorReason(
                    reason) in throw_reasons:
                if errors.ErrorReason(
                        reason) in errors.ERROR_REASON_TO_EXCEPTION:
                    raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason(
                        reason)](message)
                raise e
            if (n != retries) and (
                    is_known_error_reason and errors.ErrorReason(reason)
                    in errors.DEFAULT_RETRY_REASONS + retry_reasons):
                controlflow.wait_on_failure(n, retries, reason)
                continue
            if soft_errors:
                display.print_error(
                    f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}'
                )
                return None
            controlflow.system_error_exit(
                int(http_status), f'{http_status}: {message} - {reason}')
        except google.auth.exceptions.RefreshError as e:
            handle_oauth_token_error(
                e, soft_errors
                or errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons)
            if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons:
                raise errors.GapiServiceNotAvailableError(str(e))
            display.print_error(
                f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}')
            return None
        except ValueError as e:
            if hasattr(service._http,
                       'cache') and service._http.cache is not None:
                service._http.cache = None
                continue
            controlflow.system_error_exit(4, str(e))
        except (httplib2.ServerNotFoundError, RuntimeError) as e:
            if n != retries:
                service._http.connections = {}
                controlflow.wait_on_failure(n, retries, str(e))
                continue
            controlflow.system_error_exit(4, str(e))
        except TypeError as e:
            controlflow.system_error_exit(4, str(e))
Exemplo n.º 24
0
 def test_system_error_exit_raises_systemexit_error(self):
     with self.assertRaises(SystemExit):
         controlflow.system_error_exit(1, 'exit message')
Exemplo n.º 25
0
def doUpdateCros():
    cd = gapi.directory.buildGAPIObject()
    i, devices = getCrOSDeviceEntity(3, cd)
    update_body = {}
    action_body = {}
    orgUnitPath = None
    ack_wipe = False
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'user':
            update_body['annotatedUser'] = sys.argv[i+1]
            i += 2
        elif myarg == 'location':
            update_body['annotatedLocation'] = sys.argv[i+1]
            i += 2
        elif myarg == 'notes':
            update_body['notes'] = sys.argv[i+1].replace('\\n', '\n')
            i += 2
        elif myarg in ['tag', 'asset', 'assetid']:
            update_body['annotatedAssetId'] = sys.argv[i+1]
            i += 2
        elif myarg in ['ou', 'org']:
            orgUnitPath = __main__.getOrgUnitItem(sys.argv[i+1])
            i += 2
        elif myarg == 'action':
            action = sys.argv[i+1].lower().replace('_', '').replace('-', '')
            deprovisionReason = None
            if action in ['deprovisionsamemodelreplace',
                          'deprovisionsamemodelreplacement']:
                action = 'deprovision'
                deprovisionReason = 'same_model_replacement'
            elif action in ['deprovisiondifferentmodelreplace',
                            'deprovisiondifferentmodelreplacement']:
                action = 'deprovision'
                deprovisionReason = 'different_model_replacement'
            elif action in ['deprovisionretiringdevice']:
                action = 'deprovision'
                deprovisionReason = 'retiring_device'
            elif action not in ['disable', 'reenable']:
                controlflow.system_error_exit(2, f'expected action of ' \
                    f'deprovision_same_model_replace, ' \
                    f'deprovision_different_model_replace, ' \
                    f'deprovision_retiring_device, disable or reenable,'
                    f' got {action}')
            action_body = {'action': action}
            if deprovisionReason:
                action_body['deprovisionReason'] = deprovisionReason
            i += 2
        elif myarg == 'acknowledgedevicetouchrequirement':
            ack_wipe = True
            i += 1
        else:
            controlflow.invalid_argument_exit(sys.argv[i], "gam update cros")
    i = 0
    count = len(devices)
    if action_body:
        if action_body['action'] == 'deprovision' and not ack_wipe:
            print(f'WARNING: Refusing to deprovision {count} devices because '
                  'acknowledge_device_touch_requirement not specified. ' \
                  'Deprovisioning a device means the device will have to ' \
                  'be physically wiped and re-enrolled to be managed by ' \
                  'your domain again. This requires physical access to ' \
                  'the device and is very time consuming to perform for ' \
                  'each device. Please add ' \
                  '"acknowledge_device_touch_requirement" to the GAM ' \
                  'command if you understand this and wish to proceed ' \
                  'with the deprovision. Please also be aware that ' \
                  'deprovisioning can have an effect on your device ' \
                  'license count. See ' \
                  'https://support.google.com/chrome/a/answer/3523633 '\
                  'for full details.')
            sys.exit(3)
        for deviceId in devices:
            i += 1
            cur_count = __main__.currentCount(i, count)
            print(f' performing action {action} for {deviceId}{cur_count}')
            gapi.call(cd.chromeosdevices(), function='action',
                      customerId=GC_Values[GC_CUSTOMER_ID],
                      resourceId=deviceId, body=action_body)
    else:
        if update_body:
            for deviceId in devices:
                i += 1
                current_count = __main__.currentCount(i, count)
                print(f' updating {deviceId}{current_count}')
                gapi.call(cd.chromeosdevices(), 'update',
                          customerId=GC_Values[GC_CUSTOMER_ID],
                          deviceId=deviceId, body=update_body)
        if orgUnitPath:
            # split moves into max 50 devices per batch
            for l in range(0, len(devices), 50):
                move_body = {'deviceIds': devices[l:l+50]}
                print(f' moving {len(move_body["deviceIds"])} devices to ' \
                      f'{orgUnitPath}')
                gapi.call(cd.chromeosdevices(), 'moveDevicesToOu',
                          customerId=GC_Values[GC_CUSTOMER_ID],
                          orgUnitPath=orgUnitPath, body=move_body)
Exemplo n.º 26
0
 def test_system_error_exit_prints_error_before_exiting(
         self, mock_print_err):
     with self.assertRaises(SystemExit):
         controlflow.system_error_exit(100, 'exit message')
     self.assertIn('exit message', mock_print_err.call_args[0][0])
Exemplo n.º 27
0
 def test_system_error_exit_raises_systemexit_with_return_code(self):
     with self.assertRaises(SystemExit) as context_manager:
         controlflow.system_error_exit(100, 'exit message')
     self.assertEqual(context_manager.exception.code, 100)
Exemplo n.º 28
0
def getMatterItem(v, nameOrID):
    matterId = convertMatterNameToID(v, nameOrID)
    if not matterId:
        controlflow.system_error_exit(4, f'could not find matter {nameOrID}')
    return matterId
Exemplo n.º 29
0
def updateHold():
    v = buildGAPIObject()
    hold = sys.argv[3]
    matterId = None
    body = {}
    query = None
    add_accounts = []
    del_accounts = []
    start_time = None
    end_time = None
    i = 4
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'matter':
            matterId = getMatterItem(v, sys.argv[i+1])
            holdId = convertHoldNameToID(v, hold, matterId)
            i += 2
        elif myarg == 'query':
            query = sys.argv[i+1]
            i += 2
        elif myarg in ['orgunit', 'ou']:
            body['orgUnit'] = {'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
            i += 2
        elif myarg in ['start', 'starttime']:
            start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
            i += 2
        elif myarg in ['end', 'endtime']:
            end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
            i += 2
        elif myarg in ['addusers', 'addaccounts', 'addgroups']:
            add_accounts = sys.argv[i+1].split(',')
            i += 2
        elif myarg in ['removeusers', 'removeaccounts', 'removegroups']:
            del_accounts = sys.argv[i+1].split(',')
            i += 2
        else:
            controlflow.invalid_argument_exit(myarg, "gam update hold")
    if not matterId:
        controlflow.system_error_exit(
            3, 'you must specify a matter for the hold.')
    if query or start_time or end_time or body.get('orgUnit'):
        fields = 'corpus,query,orgUnit'
        old_body = gapi.call(v.matters().holds(
        ), 'get', matterId=matterId, holdId=holdId, fields=fields)
        body['query'] = old_body['query']
        body['corpus'] = old_body['corpus']
        if 'orgUnit' in old_body and 'orgUnit' not in body:
            # bah, API requires this to be sent
            # on update even when it's not changing
            body['orgUnit'] = old_body['orgUnit']
        query_type = f'{body["corpus"].lower()}Query'
        if body['corpus'] == 'DRIVE':
            if query:
                try:
                    body['query'][query_type] = json.loads(query)
                except ValueError as e:
                    message = f'{str(e)}, query: {query}'
                    controlflow.system_error_exit(3, message)
        elif body['corpus'] in ['GROUPS', 'MAIL']:
            if query:
                body['query'][query_type]['terms'] = query
            if start_time:
                body['query'][query_type]['startTime'] = start_time
            if end_time:
                body['query'][query_type]['endTime'] = end_time
    if body:
        print(f'Updating hold {hold} / {holdId}')
        gapi.call(v.matters().holds(), 'update',
                  matterId=matterId, holdId=holdId, body=body)
    if add_accounts or del_accounts:
        cd = __main__.buildGAPIObject('directory')
        for account in add_accounts:
            print(f'adding {account} to hold.')
            add_body = {'accountId': __main__.convertEmailAddressToUID(account, cd)}
            gapi.call(v.matters().holds().accounts(), 'create',
                      matterId=matterId, holdId=holdId, body=add_body)
        for account in del_accounts:
            print(f'removing {account} from hold.')
            accountId = __main__.convertEmailAddressToUID(account, cd)
            gapi.call(v.matters().holds().accounts(), 'delete',
                      matterId=matterId, holdId=holdId, accountId=accountId)
Exemplo n.º 30
0
def createExport():
    v = buildGAPIObject()
    allowed_corpuses = gapi.get_enum_values_minus_unspecified(
        v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
    allowed_scopes = gapi.get_enum_values_minus_unspecified(
        v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
    allowed_formats = gapi.get_enum_values_minus_unspecified(
        v._rootDesc['schemas']['MailExportOptions']['properties']
        ['exportFormat']['enum'])
    export_format = 'MBOX'
    showConfidentialModeContent = None  # default to not even set
    matterId = None
    body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}}
    i = 3
    while i < len(sys.argv):
        myarg = sys.argv[i].lower().replace('_', '')
        if myarg == 'matter':
            matterId = getMatterItem(v, sys.argv[i+1])
            body['matterId'] = matterId
            i += 2
        elif myarg == 'name':
            body['name'] = sys.argv[i+1]
            i += 2
        elif myarg == 'corpus':
            body['query']['corpus'] = sys.argv[i+1].upper()
            if body['query']['corpus'] not in allowed_corpuses:
                controlflow.expected_argument_exit(
                    "corpus", ", ".join(allowed_corpuses), sys.argv[i+1])
            i += 2
        elif myarg in VAULT_SEARCH_METHODS_MAP:
            if body['query'].get('searchMethod'):
                message = f'Multiple search methods ' \
                          f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
                          f'specified, only one is allowed'
                controlflow.system_error_exit(3, message)
            searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
            body['query']['searchMethod'] = searchMethod
            if searchMethod == 'ACCOUNT':
                body['query']['accountInfo'] = {
                    'emails': sys.argv[i+1].split(',')}
                i += 2
            elif searchMethod == 'ORG_UNIT':
                body['query']['orgUnitInfo'] = {
                    'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
                i += 2
            elif searchMethod == 'SHARED_DRIVE':
                body['query']['sharedDriveInfo'] = {
                    'sharedDriveIds': sys.argv[i+1].split(',')}
                i += 2
            elif searchMethod == 'ROOM':
                body['query']['hangoutsChatInfo'] = {
                    'roomId': sys.argv[i+1].split(',')}
                i += 2
            else:
                i += 1
        elif myarg == 'scope':
            body['query']['dataScope'] = sys.argv[i+1].upper()
            if body['query']['dataScope'] not in allowed_scopes:
                controlflow.expected_argument_exit(
                    "scope", ", ".join(allowed_scopes), sys.argv[i+1])
            i += 2
        elif myarg in ['terms']:
            body['query']['terms'] = sys.argv[i+1]
            i += 2
        elif myarg in ['start', 'starttime']:
            body['query']['startTime'] = utils.get_date_zero_time_or_full_time(
                sys.argv[i+1])
            i += 2
        elif myarg in ['end', 'endtime']:
            body['query']['endTime'] = utils.get_date_zero_time_or_full_time(
                sys.argv[i+1])
            i += 2
        elif myarg in ['timezone']:
            body['query']['timeZone'] = sys.argv[i+1]
            i += 2
        elif myarg in ['excludedrafts']:
            body['query']['mailOptions'] = {
                'excludeDrafts': __main__.getBoolean(sys.argv[i+1], myarg)}
            i += 2
        elif myarg in ['driveversiondate']:
            body['query'].setdefault('driveOptions', {})['versionDate'] = \
                utils.get_date_zero_time_or_full_time(sys.argv[i+1])
            i += 2
        elif myarg in ['includeshareddrives', 'includeteamdrives']:
            body['query'].setdefault('driveOptions', {})[
                'includeSharedDrives'] = __main__.getBoolean(sys.argv[i+1], myarg)
            i += 2
        elif myarg in ['includerooms']:
            body['query']['hangoutsChatOptions'] = {
                'includeRooms': __main__.getBoolean(sys.argv[i+1], myarg)}
            i += 2
        elif myarg in ['format']:
            export_format = sys.argv[i+1].upper()
            if export_format not in allowed_formats:
                controlflow.expected_argument_exit(
                    "export format", ", ".join(allowed_formats), export_format)
            i += 2
        elif myarg in ['showconfidentialmodecontent']:
            showConfidentialModeContent = __main__.getBoolean(sys.argv[i+1], myarg)
            i += 2
        elif myarg in ['region']:
            allowed_regions = gapi.get_enum_values_minus_unspecified(
                v._rootDesc['schemas']['ExportOptions']['properties'][
                    'region']['enum'])
            body['exportOptions']['region'] = sys.argv[i+1].upper()
            if body['exportOptions']['region'] not in allowed_regions:
                controlflow.expected_argument_exit("region", ", ".join(
                    allowed_regions), body['exportOptions']['region'])
            i += 2
        elif myarg in ['includeaccessinfo']:
            body['exportOptions'].setdefault('driveOptions', {})[
                'includeAccessInfo'] = __main__.getBoolean(sys.argv[i+1], myarg)
            i += 2
        else:
            controlflow.invalid_argument_exit(sys.argv[i], "gam create export")
    if not matterId:
        controlflow.system_error_exit(
            3, 'you must specify a matter for the new export.')
    if 'corpus' not in body['query']:
        controlflow.system_error_exit(3, f'you must specify a corpus for the ' \
          f'new export. Choose one of {", ".join(allowed_corpuses)}')
    if 'searchMethod' not in body['query']:
        controlflow.system_error_exit(3, f'you must specify a search method ' \
          'for the new export. Choose one of ' \
          f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
    if 'name' not in body:
        corpus_name = body["query"]["corpus"]
        corpus_date = datetime.datetime.now()
        body['name'] = f'GAM {corpus_name} export - {corpus_date}'
    options_field = None
    if body['query']['corpus'] == 'MAIL':
        options_field = 'mailOptions'
    elif body['query']['corpus'] == 'GROUPS':
        options_field = 'groupsOptions'
    elif body['query']['corpus'] == 'HANGOUTS_CHAT':
        options_field = 'hangoutsChatOptions'
    if options_field:
        body['exportOptions'].pop('driveOptions', None)
        body['exportOptions'][options_field] = {'exportFormat': export_format}
        if showConfidentialModeContent is not None:
            body['exportOptions'][options_field][
                'showConfidentialModeContent'] = showConfidentialModeContent
    results = gapi.call(v.matters().exports(), 'create',
                        matterId=matterId, body=body)
    print(f'Created export {results["id"]}')
    display.print_json(results)