diff --git a/src/controlflow.py b/src/controlflow.py new file mode 100644 index 000000000..20a3d1a27 --- /dev/null +++ b/src/controlflow.py @@ -0,0 +1,66 @@ +"""Methods related to the central control flow of an application.""" +import random +import sys +import time + +import display # TODO: Change to relative import when gam is setup as a package +from var import MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS +from var import MESSAGE_INVALID_JSON + + +def system_error_exit(return_code, message): + """Raises a system exit with the given return code and message. + + Args: + return_code: Int, the return code to yield when the system exits. + message: An error message to print before the system exits. + """ + if message: + display.print_error(message) + sys.exit(return_code) + + +def csv_field_error_exit(field_name, field_names): + """Raises a system exit when a CSV field is malformed. + + Args: + field_name: The CSV field name for which a header does not exist in the + existing CSV headers. + field_names: The known list of CSV headers. + """ + system_error_exit( + 2, + MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS.format(field_name, + ','.join(field_names))) + + +def invalid_json_exit(file_name): + """Raises a sysyem exit when invalid JSON content is encountered.""" + system_error_exit(17, MESSAGE_INVALID_JSON.format(file_name)) + + +def wait_on_failure(current_attempt_num, + total_num_retries, + error_message, + error_print_threshold=3): + """Executes an exponential backoff-style system sleep. + + Args: + current_attempt_num: Int, the current number of retries. + total_num_retries: Int, the total number of times the current action will be + retried. + error_message: String, a message to be displayed that will give more context + around why the action is being retried. + error_print_threshold: Int, the number of attempts which will have their + error messages suppressed. Any current_attempt_num greater than + error_print_threshold will print the prescribed error. + """ + wait_on_fail = min(2**current_attempt_num, + 60) + float(random.randint(1, 1000)) / 1000 + if current_attempt_num > error_print_threshold: + sys.stderr.write( + 'Temporary error: {0}, Backing off: {1} seconds, Retry: {2}/{3}\n' + .format(error_message, int(wait_on_fail), current_attempt_num, + total_num_retries)) + sys.stderr.flush() + time.sleep(wait_on_fail) diff --git a/src/controlflow_test.py b/src/controlflow_test.py new file mode 100644 index 000000000..4abbdd7e5 --- /dev/null +++ b/src/controlflow_test.py @@ -0,0 +1,108 @@ +"""Tests for controlflow.""" + +import unittest +from unittest.mock import patch + +import controlflow + + +class ControlFlowTest(unittest.TestCase): + + def test_system_error_exit_raises_systemexit_error(self): + with self.assertRaises(SystemExit): + controlflow.system_error_exit(1, 'exit message') + + 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) + + @patch.object(controlflow.display, 'print_error') + 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]) + + def test_csv_field_error_exit_raises_systemexit_error(self): + with self.assertRaises(SystemExit): + controlflow.csv_field_error_exit('aField', + ['unusedField1', 'unusedField2']) + + def test_csv_field_error_exit_exits_code_2(self): + with self.assertRaises(SystemExit) as context_manager: + controlflow.csv_field_error_exit('aField', + ['unusedField1', 'unusedField2']) + self.assertEqual(context_manager.exception.code, 2) + + @patch.object(controlflow.display, 'print_error') + def test_csv_field_error_exit_prints_error_details(self, mock_print_err): + with self.assertRaises(SystemExit): + controlflow.csv_field_error_exit('aField', + ['unusedField1', 'unusedField2']) + printed_message = mock_print_err.call_args[0][0] + self.assertIn('aField', printed_message) + self.assertIn('unusedField1', printed_message) + self.assertIn('unusedField2', printed_message) + + def test_invalid_json_exit_raises_systemexit_error(self): + with self.assertRaises(SystemExit): + controlflow.invalid_json_exit('filename') + + def test_invalid_json_exit_exit_exits_code_17(self): + with self.assertRaises(SystemExit) as context_manager: + controlflow.invalid_json_exit('filename') + self.assertEqual(context_manager.exception.code, 17) + + @patch.object(controlflow.display, 'print_error') + def test_invalid_json_exit_prints_error_details(self, mock_print_err): + with self.assertRaises(SystemExit): + controlflow.invalid_json_exit('filename') + printed_message = mock_print_err.call_args[0][0] + self.assertIn('filename', printed_message) + + @patch.object(controlflow.time, 'sleep') + def test_wait_on_failure_waits_exponentially(self, mock_sleep): + controlflow.wait_on_failure(1, 5, 'Backoff attempt #1') + controlflow.wait_on_failure(2, 5, 'Backoff attempt #2') + controlflow.wait_on_failure(3, 5, 'Backoff attempt #3') + + sleep_calls = mock_sleep.call_args_list + self.assertGreaterEqual(sleep_calls[0][0][0], 2**1) + self.assertGreaterEqual(sleep_calls[1][0][0], 2**2) + self.assertGreaterEqual(sleep_calls[2][0][0], 2**3) + + @patch.object(controlflow.time, 'sleep') + def test_wait_on_failure_does_not_exceed_60_secs_wait(self, mock_sleep): + total_attempts = 20 + for attempt in range(1, total_attempts + 1): + controlflow.wait_on_failure( + attempt, + total_attempts, + 'Attempt #%s' % attempt, + # Suppress messages while we make a lot of attempts. + error_print_threshold=total_attempts + 1) + # Wait time may be between 60 and 61 secs, due to rand addition. + self.assertLess(mock_sleep.call_args[0][0], 61) + + # Prevent the system from actually sleeping and thus slowing down the test. + @patch.object(controlflow.time, 'sleep') + @patch.object(controlflow.sys.stderr, 'write') + def test_wait_on_failure_prints_errors(self, mock_stderr_write, + unused_mock_sleep): + message = 'An error message to display' + controlflow.wait_on_failure(1, 5, message, error_print_threshold=0) + self.assertIn(message, mock_stderr_write.call_args[0][0]) + + @patch.object(controlflow.time, 'sleep') + @patch.object(controlflow.sys.stderr, 'write') + def test_wait_on_failure_only_prints_after_threshold(self, mock_stderr_write, + unused_mock_sleep): + total_attempts = 5 + threshold = 3 + for attempt in range(1, total_attempts + 1): + controlflow.wait_on_failure( + attempt, + total_attempts, + 'Attempt #%s' % attempt, + error_print_threshold=threshold) + self.assertEqual(total_attempts - threshold, mock_stderr_write.call_count) diff --git a/src/display.py b/src/display.py new file mode 100644 index 000000000..5b520d6ba --- /dev/null +++ b/src/display.py @@ -0,0 +1,18 @@ +"""Methods related to display of information to the user.""" + +import sys +import utils +from var import ERROR_PREFIX +from var import WARNING_PREFIX + + +def print_error(message): + """Prints a one-line error message to stderr in a standard format.""" + sys.stderr.write( + utils.convertUTF8('\n{0}{1}\n'.format(ERROR_PREFIX, message))) + + +def print_warning(message): + """Prints a one-line warning message to stderr in a standard format.""" + sys.stderr.write( + utils.convertUTF8('\n{0}{1}\n'.format(WARNING_PREFIX, message))) diff --git a/src/display_test.py b/src/display_test.py new file mode 100644 index 000000000..d2e38d0cd --- /dev/null +++ b/src/display_test.py @@ -0,0 +1,59 @@ +"""Tests for display.""" + +import unittest +from unittest.mock import patch + +import display +from var import ERROR_PREFIX +from var import WARNING_PREFIX + + +class DisplayTest(unittest.TestCase): + + @patch.object(display.sys.stderr, 'write') + def test_print_error_prints_to_stderr(self, mock_write): + message = 'test error' + display.print_error(message) + printed_message = mock_write.call_args[0][0] + self.assertIn(message, printed_message) + + @patch.object(display.sys.stderr, 'write') + def test_print_error_prints_error_prefix(self, mock_write): + message = 'test error' + display.print_error(message) + printed_message = mock_write.call_args[0][0] + self.assertLess( + printed_message.find(ERROR_PREFIX), printed_message.find(message), + 'The error prefix does not appear before the error message') + + @patch.object(display.sys.stderr, 'write') + def test_print_error_ends_message_with_newline(self, mock_write): + message = 'test error' + display.print_error(message) + printed_message = mock_write.call_args[0][0] + self.assertRegex(printed_message, '\n$', + 'The error message does not end in a newline.') + + @patch.object(display.sys.stderr, 'write') + def test_print_warning_prints_to_stderr(self, mock_write): + message = 'test warning' + display.print_warning(message) + printed_message = mock_write.call_args[0][0] + self.assertIn(message, printed_message) + + @patch.object(display.sys.stderr, 'write') + def test_print_warning_prints_error_prefix(self, mock_write): + message = 'test warning' + display.print_error(message) + printed_message = mock_write.call_args[0][0] + self.assertLess( + printed_message.find(WARNING_PREFIX), printed_message.find(message), + 'The warning prefix does not appear before the error message') + + @patch.object(display.sys.stderr, 'write') + def test_print_warning_ends_message_with_newline(self, mock_write): + message = 'test warning' + display.print_error(message) + printed_message = mock_write.call_args[0][0] + self.assertRegex(printed_message, '\n$', + 'The warning message does not end in a newline.') diff --git a/src/gam.py b/src/gam.py index 53672533a..0f8464cf1 100755 --- a/src/gam.py +++ b/src/gam.py @@ -74,6 +74,10 @@ import google_auth_httplib2 import httplib2 +import controlflow +import display +import gapi.errors +import gapi import utils from var import * @@ -116,11 +120,6 @@ def wrapped_request_method(self, *args, **kwargs): google_auth_httplib2.AuthorizedHttp.request = _request_with_user_agent( google_auth_httplib2.AuthorizedHttp.request) -def _createHttpObj(cache=None, timeout=None, override_min_tls=None, override_max_tls=None): - tls_minimum_version = override_min_tls if override_min_tls else GC_Values[GC_TLS_MIN_VERSION] - tls_maximum_version = override_max_tls if override_max_tls else GC_Values[GC_TLS_MAX_VERSION] - return httplib2.Http(ca_certs=GC_Values[GC_CA_FILE], tls_maximum_version=tls_maximum_version, tls_minimum_version=tls_minimum_version, - cache=cache, timeout=timeout) def showUsage(): doGAMVersion(checkForArgs=False) @@ -139,22 +138,6 @@ def showUsage(): ... ''') -# -# Error handling -# -def stderrErrorMsg(message): - sys.stderr.write(utils.convertUTF8('\n{0}{1}\n'.format(ERROR_PREFIX, message))) - -def stderrWarningMsg(message): - sys.stderr.write(utils.convertUTF8('\n{0}{1}\n'.format(WARNING_PREFIX, message))) - -def systemErrorExit(sysRC, message): - if message: - stderrErrorMsg(message) - sys.exit(sysRC) - -def invalidJSONExit(fileName): - systemErrorExit(17, MESSAGE_INVALID_JSON.format(fileName)) def currentCount(i, count): return ' ({0}/{1})'.format(i, count) if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else '' @@ -162,14 +145,6 @@ def currentCount(i, count): def currentCountNL(i, count): return ' ({0}/{1})\n'.format(i, count) if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else '\n' -def formatHTTPError(http_status, reason, message): - return '{0}: {1} - {2}'.format(http_status, reason, message) - -def getHTTPError(responses, http_status, reason, message): - if reason in responses: - return responses[reason] - return formatHTTPError(http_status, reason, message) - def printGettingAllItems(items, query): if query: sys.stderr.write("Getting all {0} in G Suite account that match query ({1}) (may take some time on a large account)...\n".format(items, query)) @@ -189,20 +164,16 @@ def entityUnknownWarning(entityType, entityName, i, count): else: entityServiceNotApplicableWarning(entityType, entityName, i, count) -# Invalid CSV ~Header or ~~Header~~ -def csvFieldErrorExit(fieldName, fieldNames): - systemErrorExit(2, MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS.format(fieldName, ','.join(fieldNames))) - def printLine(message): sys.stdout.write(message+'\n') -# + def getBoolean(value, item): value = value.lower() if value in true_values: return True if value in false_values: return False - systemErrorExit(2, 'Value for {0} must be {1} or {2}; got {3}'.format(item, '|'.join(true_values), '|'.join(false_values), value)) + controlflow.system_error_exit(2, 'Value for {0} must be {1} or {2}; got {3}'.format(item, '|'.join(true_values), '|'.join(false_values), value)) def getCharSet(i): if (i == len(sys.argv)) or (sys.argv[i].lower() != 'charset'): @@ -253,7 +224,7 @@ def getColor(color): tg = COLORHEX_PATTERN.match(color) if tg: return tg.group(0) - systemErrorExit(2, 'A color must be a valid name or # and six hex characters (#012345); got {0}'.format(color)) + controlflow.system_error_exit(2, 'A color must be a valid name or # and six hex characters (#012345); got {0}'.format(color)) def getLabelColor(color): color = color.lower().strip() @@ -262,8 +233,8 @@ def getLabelColor(color): color = tg.group(0) if color in LABEL_COLORS: return color - systemErrorExit(2, 'A label color must be in the list: {0}; got {1}'.format('|'.join(LABEL_COLORS), color)) - systemErrorExit(2, 'A label color must be # and six hex characters (#012345); got {0}'.format(color)) + controlflow.system_error_exit(2, 'A label color must be in the list: {0}; got {1}'.format('|'.join(LABEL_COLORS), color)) + controlflow.system_error_exit(2, 'A label color must be # and six hex characters (#012345); got {0}'.format(color)) def integerLimits(minVal, maxVal, item='integer'): if (minVal is not None) and (maxVal is not None): @@ -281,7 +252,7 @@ def getInteger(value, item, minVal=None, maxVal=None): return number except ValueError: pass - systemErrorExit(2, 'expected {0} in range <{1}>, got {2}'.format(item, integerLimits(minVal, maxVal), value)) + controlflow.system_error_exit(2, 'expected {0} in range <{1}>, got {2}'.format(item, integerLimits(minVal, maxVal), value)) def removeCourseIdScope(courseId): if courseId.startswith('d:'): @@ -299,13 +270,13 @@ def getString(i, item, optional=False, minLen=1, maxLen=None): if argstr: if (len(argstr) >= minLen) and ((maxLen is None) or (len(argstr) <= maxLen)): return argstr - systemErrorExit(2, 'expected <{0} for {1}>'.format(integerLimits(minLen, maxLen, 'string length'), item)) + controlflow.system_error_exit(2, 'expected <{0} for {1}>'.format(integerLimits(minLen, maxLen, 'string length'), item)) if optional or (minLen == 0): return '' - systemErrorExit(2, 'expected a Non-empty <{0}>'.format(item)) + controlflow.system_error_exit(2, 'expected a Non-empty <{0}>'.format(item)) elif optional: return '' - systemErrorExit(2, 'expected a <{0}>'.format(item)) + controlflow.system_error_exit(2, 'expected a <{0}>'.format(item)) def getDelta(argstr, pattern): tg = pattern.match(argstr.lower()) @@ -334,7 +305,7 @@ def getDelta(argstr, pattern): def getDeltaDate(argstr): deltaDate = getDelta(argstr, DELTA_DATE_PATTERN) if deltaDate is None: - systemErrorExit(2, 'expected a <{0}>; got {1}'.format(DELTA_DATE_FORMAT_REQUIRED, argstr)) + controlflow.system_error_exit(2, 'expected a <{0}>; got {1}'.format(DELTA_DATE_FORMAT_REQUIRED, argstr)) return deltaDate DELTA_TIME_PATTERN = re.compile(r'^([+-])(\d+)([mhdwy])$') @@ -343,7 +314,7 @@ def getDeltaDate(argstr): def getDeltaTime(argstr): deltaTime = getDelta(argstr, DELTA_TIME_PATTERN) if deltaTime is None: - systemErrorExit(2, 'expected a <{0}>; got {1}'.format(DELTA_TIME_FORMAT_REQUIRED, argstr)) + controlflow.system_error_exit(2, 'expected a <{0}>; got {1}'.format(DELTA_TIME_FORMAT_REQUIRED, argstr)) return deltaTime YYYYMMDD_FORMAT = '%Y-%m-%d' @@ -363,10 +334,10 @@ def getYYYYMMDD(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False): return dateTime return argstr except ValueError: - systemErrorExit(2, 'expected a <{0}>; got {1}'.format(YYYYMMDD_FORMAT_REQUIRED, argstr)) + controlflow.system_error_exit(2, 'expected a <{0}>; got {1}'.format(YYYYMMDD_FORMAT_REQUIRED, argstr)) elif minLen == 0: return '' - systemErrorExit(2, 'expected a <{0}>'.format(YYYYMMDD_FORMAT_REQUIRED)) + controlflow.system_error_exit(2, 'expected a <{0}>'.format(YYYYMMDD_FORMAT_REQUIRED)) YYYYMMDDTHHMMSS_FORMAT_REQUIRED = 'yyyy-mm-ddThh:mm:ss[.fff](Z|(+|-(hh:mm)))' @@ -382,7 +353,7 @@ def getTimeOrDeltaFromNow(time_string): if time_string[0] not in ['+', '-']: return time_string return (datetime.datetime.utcnow() + getDeltaTime(time_string)).isoformat() + 'Z' - systemErrorExit(2, 'expected a <{0}>'.format(YYYYMMDDTHHMMSS_FORMAT_REQUIRED)) + controlflow.system_error_exit(2, 'expected a <{0}>'.format(YYYYMMDDTHHMMSS_FORMAT_REQUIRED)) def getRowFilterDateOrDeltaFromNow(date_string): """Get an ISO 8601 date or a positive/negative delta applied to now. @@ -437,7 +408,7 @@ def getDateZeroTimeOrFullTime(time_string): if YYYYMMDD_PATTERN.match(time_string): return getYYYYMMDD(time_string)+'T00:00:00.000Z' return getTimeOrDeltaFromNow(time_string) - systemErrorExit(2, 'expected a <{0}>'.format(YYYYMMDDTHHMMSS_FORMAT_REQUIRED)) + controlflow.system_error_exit(2, 'expected a <{0}>'.format(YYYYMMDDTHHMMSS_FORMAT_REQUIRED)) # Get domain from email address def getEmailAddressDomain(emailAddress): @@ -518,7 +489,7 @@ def openFile(filename, mode='r', encoding=None, newline=None, return io.StringIO(str(sys.stdin.read())) return sys.stdout except IOError as e: - systemErrorExit(6, e) + controlflow.system_error_exit(6, e) # # Close a file # @@ -527,7 +498,7 @@ def closeFile(f): f.close() return True except IOError as e: - stderrErrorMsg(e) + display.print_error(e) return False # # Read a file @@ -543,11 +514,11 @@ def readFile(filename, mode='r', encoding=None, newline=None, except IOError as e: if continueOnError: if displayError: - stderrWarningMsg(e) + display.print_warning(e) return None - systemErrorExit(6, e) + controlflow.system_error_exit(6, e) except (LookupError, UnicodeDecodeError, UnicodeError) as e: - systemErrorExit(2, str(e)) + controlflow.system_error_exit(2, str(e)) # # Write a file # @@ -560,9 +531,9 @@ def writeFile(filename, data, mode='w', continueOnError=False, displayError=True except IOError as e: if continueOnError: if displayError: - stderrErrorMsg(e) + display.print_error(e) return False - systemErrorExit(6, e) + controlflow.system_error_exit(6, e) # # Set global variables # Check for GAM updates based on status of noupdatecheck.txt @@ -608,7 +579,7 @@ def _getCfgHeaderFilter(itemName): try: headerFilters.append(re.compile(filterStr, re.IGNORECASE)) except re.error as e: - systemErrorExit(3, 'Item: {0}: "{1}", Invalid RE: {2}'.format(itemName, filterStr, e)) + controlflow.system_error_exit(3, 'Item: {0}: "{1}", Invalid RE: {2}'.format(itemName, filterStr, e)) return headerFilters ROW_FILTER_COMP_PATTERN = re.compile(r'^(date|time|count)\s*([<>]=?|=|!=)\s*(.+)$', re.IGNORECASE) @@ -624,19 +595,19 @@ def _getCfgRowFilter(itemName): try: filterDict = json.loads(value.encode('unicode-escape').decode(UTF8)) except (TypeError, ValueError) as e: - systemErrorExit(3, 'Item: {0}, Value: "{1}", Failed to parse as JSON: {2}'.format(itemName, value, str(e))) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}", Failed to parse as JSON: {2}'.format(itemName, value, str(e))) else: filterDict = {} status, filterList = shlexSplitListStatus(value) if not status: - systemErrorExit(3, 'Item: {0}, Value: "{1}", Failed to parse as list'.format(itemName, value)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}", Failed to parse as list'.format(itemName, value)) for filterVal in filterList: if not filterVal: continue try: column, filterStr = filterVal.split(':', 1) except ValueError: - systemErrorExit(3, 'Item: {0}, Value: "{1}", Expected column:filter'.format(itemName, filterVal)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}", Expected column:filter'.format(itemName, filterVal)) filterDict[column] = filterStr for column, filterStr in iter(filterDict.items()): mg = ROW_FILTER_COMP_PATTERN.match(filterStr) @@ -649,12 +620,12 @@ def _getCfgRowFilter(itemName): if valid: rowFilters[column] = (mg.group(1), mg.group(2), filterValue) continue - systemErrorExit(3, 'Item: {0}, Value: "{1}": "{2}", Expected: {3}'.format(itemName, column, filterStr, filterValue)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}": "{2}", Expected: {3}'.format(itemName, column, filterStr, filterValue)) else: #count if mg.group(3).isdigit(): rowFilters[column] = (mg.group(1), mg.group(2), int(mg.group(3))) continue - systemErrorExit(3, 'Item: {0}, Value: "{1}": "{2}", Expected: {3}'.format(itemName, column, filterStr, '')) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}": "{2}", Expected: {3}'.format(itemName, column, filterStr, '')) mg = ROW_FILTER_BOOL_PATTERN.match(filterStr) if mg: value = mg.group(2).lower() @@ -663,7 +634,7 @@ def _getCfgRowFilter(itemName): elif value in false_values: filterValue = False else: - systemErrorExit(3, 'Item: {0}, Value: "{1}": "{2}", Expected true|false'.format(itemName, column, filterStr)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}": "{2}", Expected true|false'.format(itemName, column, filterStr)) rowFilters[column] = (mg.group(1), filterValue) continue mg = ROW_FILTER_RE_PATTERN.match(filterStr) @@ -672,8 +643,8 @@ def _getCfgRowFilter(itemName): rowFilters[column] = (mg.group(1), re.compile(mg.group(2))) continue except re.error as e: - systemErrorExit(3, 'Item: {0}, Value: "{1}": "{2}", Invalid RE: {3}'.format(itemName, column, filterStr, e)) - systemErrorExit(3, 'Item: {0}, Value: "{1}": {2}, Expected: (date|time|count) or (boolean:true|false) or (regex|notregex:)'.format(itemName, column, filterStr)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}": "{2}", Invalid RE: {3}'.format(itemName, column, filterStr, e)) + controlflow.system_error_exit(3, 'Item: {0}, Value: "{1}": {2}, Expected: (date|time|count) or (boolean:true|false) or (regex|notregex:)'.format(itemName, column, filterStr)) return rowFilters GC_Defaults[GC_CONFIG_DIR] = GM_Globals[GM_GAM_PATH] @@ -753,11 +724,11 @@ def getLocalGoogleTimeOffset(testLocation='www.googleapis.com'): # we disable SSL verify so we can still get time even if clock # is way off. This could be spoofed / MitM but we'll fail for those # situations everywhere else but here. - badhttp = _createHttpObj() + badhttp = gapi.create_http() badhttp.disable_ssl_certificate_validation = True googleUTC = dateutil.parser.parse(badhttp.request('https://'+testLocation, 'HEAD')[0]['date']) except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e: - systemErrorExit(4, str(e)) + controlflow.system_error_exit(4, str(e)) offset = remainder = int(abs((localUTC-googleUTC).total_seconds())) timeoff = [] for tou in TIME_OFFSET_UNITS: @@ -773,7 +744,7 @@ def doGAMCheckForUpdates(forceCheck=False): def _gamLatestVersionNotAvailable(): if forceCheck: - systemErrorExit(4, 'GAM Latest Version information not available') + controlflow.system_error_exit(4, 'GAM Latest Version information not available') current_version = gam_version now_time = int(time.time()) @@ -786,7 +757,7 @@ def _gamLatestVersionNotAvailable(): return check_url = GAM_LATEST_RELEASE # latest full release headers = {'Accept': 'application/vnd.github.v3.text+json'} - simplehttp = _createHttpObj(timeout=10) + simplehttp = gapi.create_http(timeout=10) try: (_, c) = simplehttp.request(check_url, 'GET', headers=headers) try: @@ -862,7 +833,7 @@ def doGAMVersion(checkForArgs=True): testLocation = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam version"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam version"' % sys.argv[i]) if simple: sys.stdout.write(gam_version) return @@ -877,7 +848,7 @@ def doGAMVersion(checkForArgs=True): offset, nicetime = getLocalGoogleTimeOffset(testLocation) print(MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY % nicetime) if offset > MAX_LOCAL_GOOGLE_TIME_OFFSET: - systemErrorExit(4, 'Please fix your system time.') + controlflow.system_error_exit(4, 'Please fix your system time.') if force_check: doGAMCheckForUpdates(forceCheck=True) if extended: @@ -889,7 +860,7 @@ def _getServerTLSUsed(location): url = 'https://%s' % location _, netloc, _, _, _, _ = urlparse(url) conn = 'https:%s' % netloc - httpc = _createHttpObj() + httpc = gapi.create_http() headers = {'user-agent': GAM_INFO} retries = 5 for n in range(1, retries+1): @@ -899,31 +870,19 @@ def _getServerTLSUsed(location): except (httplib2.ServerNotFoundError, RuntimeError) as e: if n != retries: httpc.connections = {} - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(4, str(e)) + controlflow.system_error_exit(4, str(e)) cipher_name, tls_ver, _ = httpc.connections[conn].sock.cipher() return tls_ver, cipher_name -def handleOAuthTokenError(e, soft_errors): - if e.replace('.', '') in OAUTH2_TOKEN_ERRORS or e.startswith('Invalid response'): - if soft_errors: - return None - if not GM_Globals[GM_CURRENT_API_USER]: - stderrErrorMsg(MESSAGE_API_ACCESS_DENIED.format(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID], - ','.join(GM_Globals[GM_CURRENT_API_SCOPES]))) - systemErrorExit(12, MESSAGE_API_ACCESS_CONFIG) - else: - systemErrorExit(19, MESSAGE_SERVICE_NOT_APPLICABLE.format(GM_Globals[GM_CURRENT_API_USER])) - systemErrorExit(18, 'Authentication Token Error - {0}'.format(e)) - def getSvcAcctCredentials(scopes, act_as): try: if not GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]: json_string = readFile(GC_Values[GC_OAUTH2SERVICE_JSON], continueOnError=True, displayError=True) if not json_string: printLine(MESSAGE_INSTRUCTIONS_OAUTH2SERVICE_JSON) - systemErrorExit(6, None) + controlflow.system_error_exit(6, None) GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string) credentials = google.oauth2.service_account.Credentials.from_service_account_info(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]) credentials = credentials.with_scopes(scopes) @@ -933,224 +892,7 @@ def getSvcAcctCredentials(scopes, act_as): return credentials except (ValueError, KeyError): printLine(MESSAGE_INSTRUCTIONS_OAUTH2SERVICE_JSON) - invalidJSONExit(GC_Values[GC_OAUTH2SERVICE_JSON]) - -def waitOnFailure(n, retries, errMsg): - wait_on_fail = min(2 ** n, 60) + float(random.randint(1, 1000)) / 1000 - if n > 3: - sys.stderr.write('Temporary error: {0}, Backing off: {1} seconds, Retry: {2}/{3}\n'.format(errMsg, int(wait_on_fail), n, retries)) - sys.stderr.flush() - time.sleep(wait_on_fail) - -def checkGAPIError(e, soft_errors=False, silent_errors=False, retryOnHttpError=False, service=None): - try: - error = json.loads(e.content.decode(UTF8)) - except ValueError: - eContent = e.content.decode(UTF8) if isinstance(e.content, bytes) else e.content - if (e.resp['status'] == '503') and (eContent == 'Quota exceeded for the current request'): - return (e.resp['status'], GAPI_QUOTA_EXCEEDED, eContent) - if (e.resp['status'] == '403') and (eContent.startswith('Request rate higher than configured')): - return (e.resp['status'], GAPI_QUOTA_EXCEEDED, eContent) - if (e.resp['status'] == '403') and ('Invalid domain.' in eContent): - error = {'error': {'code': 403, 'errors': [{'reason': GAPI_NOT_FOUND, 'message': 'Domain not found'}]}} - elif (e.resp['status'] == '400') and ('InvalidSsoSigningKey' in eContent): - error = {'error': {'code': 400, 'errors': [{'reason': GAPI_INVALID, 'message': 'InvalidSsoSigningKey'}]}} - elif (e.resp['status'] == '400') and ('UnknownError' in eContent): - error = {'error': {'code': 400, 'errors': [{'reason': GAPI_INVALID, 'message': 'UnknownError'}]}} - elif retryOnHttpError: - service._http.request.credentials.refresh(_createHttpObj()) - return (-1, None, None) - elif soft_errors: - if not silent_errors: - stderrErrorMsg(eContent) - return (0, None, None) - else: - systemErrorExit(5, eContent) - 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 = {'error': {'errors': [{'reason': GAPI_INVALID, 'message': message}]}} - else: - systemErrorExit(4, str(error)) - else: - systemErrorExit(4, str(error)) - try: - reason = error['error']['errors'][0]['reason'] - if reason == 'notFound': - if 'userKey' in message: - reason = GAPI_USER_NOT_FOUND - elif 'groupKey' in message: - reason = GAPI_GROUP_NOT_FOUND - elif 'memberKey' in message: - reason = GAPI_MEMBER_NOT_FOUND - elif 'Domain not found' in message: - reason = GAPI_DOMAIN_NOT_FOUND - elif 'Resource Not Found' in message: - reason = GAPI_RESOURCE_NOT_FOUND - elif reason == 'invalid': - if 'userId' in message: - reason = GAPI_USER_NOT_FOUND - elif 'memberKey' in message: - reason = GAPI_INVALID_MEMBER - elif reason == 'failedPrecondition': - if 'Bad Request' in message: - reason = GAPI_BAD_REQUEST - elif 'Mail service not enabled' in message: - reason = GAPI_SERVICE_NOT_AVAILABLE - elif reason == 'required': - if 'memberKey' in message: - reason = GAPI_MEMBER_NOT_FOUND - elif reason == 'conditionNotMet': - if 'Cyclic memberships not allowed' in message: - reason = GAPI_CYCLIC_MEMBERSHIPS_NOT_ALLOWED - except KeyError: - reason = '{0}'.format(http_status) - return (http_status, reason, message) - -class GAPI_aborted(Exception): - pass -class GAPI_authError(Exception): - pass -class GAPI_badRequest(Exception): - pass -class GAPI_conditionNotMet(Exception): - pass -class GAPI_cyclicMembershipsNotAllowed(Exception): - pass -class GAPI_domainCannotUseApis(Exception): - pass -class GAPI_domainNotFound(Exception): - pass -class GAPI_duplicate(Exception): - pass -class GAPI_failedPrecondition(Exception): - pass -class GAPI_forbidden(Exception): - pass -class GAPI_groupNotFound(Exception): - pass -class GAPI_invalid(Exception): - pass -class GAPI_invalidArgument(Exception): - pass -class GAPI_invalidMember(Exception): - pass -class GAPI_memberNotFound(Exception): - pass -class GAPI_notFound(Exception): - pass -class GAPI_notImplemented(Exception): - pass -class GAPI_permissionDenied(Exception): - pass -class GAPI_resourceNotFound(Exception): - pass -class GAPI_serviceNotAvailable(Exception): - pass -class GAPI_userNotFound(Exception): - pass - -GAPI_REASON_EXCEPTION_MAP = { - GAPI_ABORTED: GAPI_aborted, - GAPI_AUTH_ERROR: GAPI_authError, - GAPI_BAD_REQUEST: GAPI_badRequest, - GAPI_CONDITION_NOT_MET: GAPI_conditionNotMet, - GAPI_CYCLIC_MEMBERSHIPS_NOT_ALLOWED: GAPI_cyclicMembershipsNotAllowed, - GAPI_DOMAIN_CANNOT_USE_APIS: GAPI_domainCannotUseApis, - GAPI_DOMAIN_NOT_FOUND: GAPI_domainNotFound, - GAPI_DUPLICATE: GAPI_duplicate, - GAPI_FAILED_PRECONDITION: GAPI_failedPrecondition, - GAPI_FORBIDDEN: GAPI_forbidden, - GAPI_GROUP_NOT_FOUND: GAPI_groupNotFound, - GAPI_INVALID: GAPI_invalid, - GAPI_INVALID_ARGUMENT: GAPI_invalidArgument, - GAPI_INVALID_MEMBER: GAPI_invalidMember, - GAPI_MEMBER_NOT_FOUND: GAPI_memberNotFound, - GAPI_NOT_FOUND: GAPI_notFound, - GAPI_NOT_IMPLEMENTED: GAPI_notImplemented, - GAPI_PERMISSION_DENIED: GAPI_permissionDenied, - GAPI_RESOURCE_NOT_FOUND: GAPI_resourceNotFound, - GAPI_SERVICE_NOT_AVAILABLE: GAPI_serviceNotAvailable, - GAPI_USER_NOT_FOUND: GAPI_userNotFound, - } - -def callGAPI(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. - - Returns: - The given Google service function's response object. - """ - 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 = checkGAPIError(e, soft_errors=soft_errors, silent_errors=silent_errors, retryOnHttpError=n < 3, service=service) - if http_status == -1: - continue - if http_status == 0: - return None - if reason in throw_reasons: - if reason in GAPI_REASON_EXCEPTION_MAP: - raise GAPI_REASON_EXCEPTION_MAP[reason](message) - raise e - if (n != retries) and (reason in GAPI_DEFAULT_RETRY_REASONS+retry_reasons): - waitOnFailure(n, retries, reason) - continue - if soft_errors: - stderrErrorMsg('{0}: {1} - {2}{3}'.format(http_status, message, reason, ['', ': Giving up.'][n > 1])) - return None - systemErrorExit(int(http_status), '{0}: {1} - {2}'.format(http_status, message, reason)) - except google.auth.exceptions.RefreshError as e: - handleOAuthTokenError(str(e), soft_errors or GAPI_SERVICE_NOT_AVAILABLE in throw_reasons) - if GAPI_SERVICE_NOT_AVAILABLE in throw_reasons: - raise GAPI_serviceNotAvailable(str(e)) - stderrErrorMsg('User {0}: {1}'.format(GM_Globals[GM_CURRENT_API_USER], str(e))) - return None - except ValueError as e: - if service._http.cache is not None: - service._http.cache = None - continue - systemErrorExit(4, str(e)) - except (httplib2.ServerNotFoundError, RuntimeError) as e: - if n != retries: - service._http.connections = {} - waitOnFailure(n, retries, str(e)) - continue - systemErrorExit(4, str(e)) - except TypeError as e: - systemErrorExit(4, str(e)) + controlflow.invalid_json_exit(GC_Values[GC_OAUTH2SERVICE_JSON]) def getPageSize(service, function, kwargs): """Gets maximum maxResults value for API call. Uses value from discovery if @@ -1217,7 +959,7 @@ def callGAPIpages(service, function, items='items', page_token = None total_items = 0 while True: - page = callGAPI(service, + page = gapi.call(service, function, soft_errors=soft_errors, throw_reasons=throw_reasons, @@ -1274,7 +1016,7 @@ def callGAPIitems(service, function, items='items', Returns: The list of items in the first page of a response. """ - results = callGAPI(service, + results = gapi.call(service, function, throw_reasons=throw_reasons, retry_reasons=retry_reasons, @@ -1303,12 +1045,12 @@ def readDiscoveryFile(api_version): elif pyinstaller_disc_file: json_string = readFile(pyinstaller_disc_file) else: - systemErrorExit(11, MESSAGE_NO_DISCOVERY_INFORMATION.format(disc_file)) + controlflow.system_error_exit(11, MESSAGE_NO_DISCOVERY_INFORMATION.format(disc_file)) try: discovery = json.loads(json_string) return (disc_file, discovery) except ValueError: - invalidJSONExit(disc_file) + controlflow.invalid_json_exit(disc_file) def getOauth2TxtStorageCredentials(): oauth_string = readFile(GC_Values[GC_OAUTH2_TXT], continueOnError=True, displayError=False) @@ -1335,16 +1077,16 @@ def getValidOauth2TxtCredentials(force_refresh=False): retries = 3 for n in range(1, retries+1): try: - credentials.refresh(google_auth_httplib2.Request(_createHttpObj())) + credentials.refresh(google_auth_httplib2.Request(gapi.create_http())) writeCredentials(credentials) break except google.auth.exceptions.RefreshError as e: - systemErrorExit(18, str(e)) + controlflow.system_error_exit(18, str(e)) except (google.auth.exceptions.TransportError, httplib2.ServerNotFoundError, RuntimeError) as e: if n != retries: - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(4, str(e)) + controlflow.system_error_exit(4, str(e)) elif credentials is None or not credentials.valid: doRequestOAuth() credentials = getOauth2TxtStorageCredentials() @@ -1373,20 +1115,20 @@ def getService(api, http): except (httplib2.ServerNotFoundError, RuntimeError) as e: if n != retries: http.connections = {} - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(4, str(e)) + controlflow.system_error_exit(4, str(e)) except (googleapiclient.errors.InvalidJsonError, KeyError, ValueError) as e: http.cache = None if n != retries: - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(17, str(e)) + controlflow.system_error_exit(17, str(e)) except (http_client.ResponseNotReady, socket.error) as e: if n != retries: - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(3, str(e)) + controlflow.system_error_exit(3, str(e)) except googleapiclient.errors.UnknownApiNameOrVersion: break disc_file, discovery = readDiscoveryFile(api_version) @@ -1398,13 +1140,13 @@ def getService(api, http): http.cache = None return service except (KeyError, ValueError): - invalidJSONExit(disc_file) + controlflow.invalid_json_exit(disc_file) def buildGAPIObject(api): GM_Globals[GM_CURRENT_API_USER] = None credentials = getValidOauth2TxtCredentials() credentials.user_agent = GAM_INFO - http = google_auth_httplib2.AuthorizedHttp(credentials, _createHttpObj(cache=GM_Globals[GM_CACHE_DIR])) + http = google_auth_httplib2.AuthorizedHttp(credentials, gapi.create_http(cache=GM_Globals[GM_CACHE_DIR])) service = getService(api, http) if GC_Values[GC_DOMAIN]: if not GC_Values[GC_CUSTOMER_ID]: @@ -1412,13 +1154,13 @@ def buildGAPIObject(api): try: resultObj = json.loads(result) except ValueError: - systemErrorExit(8, 'Unexpected response: {0}'.format(result)) + controlflow.system_error_exit(8, 'Unexpected response: {0}'.format(result)) if resp['status'] in ['403', '404']: try: message = resultObj['error']['errors'][0]['message'] except KeyError: message = resultObj['error']['message'] - systemErrorExit(8, '{0} - {1}'.format(message, GC_Values[GC_DOMAIN])) + controlflow.system_error_exit(8, '{0} - {1}'.format(message, GC_Values[GC_DOMAIN])) try: GC_Values[GC_CUSTOMER_ID] = resultObj['users'][0]['customerId'] except KeyError: @@ -1440,31 +1182,31 @@ def convertUIDtoEmailAddress(emailAddressOrUID, cd=None, email_types=['user']): cd = buildGAPIObject('directory') if 'user' in email_types: try: - result = callGAPI(cd.users(), 'get', - throw_reasons=[GAPI_USER_NOT_FOUND], + result = gapi.call(cd.users(), 'get', + throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND], userKey=normalizedEmailAddressOrUID, fields='primaryEmail') if 'primaryEmail' in result: return result['primaryEmail'].lower() - except GAPI_userNotFound: + except gapi.errors.GapiUserNotFoundError: pass if 'group' in email_types: try: - result = callGAPI(cd.groups(), 'get', - throw_reasons=[GAPI_GROUP_NOT_FOUND], + result = gapi.call(cd.groups(), 'get', + throw_reasons=[gapi.errors.ErrorReason.GROUP_NOT_FOUND], groupKey=normalizedEmailAddressOrUID, fields='email') if 'email' in result: return result['email'].lower() - except GAPI_groupNotFound: + except gapi.errors.GapiGroupNotFoundError: pass if 'resource' in email_types: try: - result = callGAPI(cd.resources().calendars(), 'get', - throw_reasons=[GAPI_RESOURCE_NOT_FOUND], + result = gapi.call(cd.resources().calendars(), 'get', + throw_reasons=[gapi.errors.ErrorReason.RESOURCE_NOT_FOUND], calendarResourceId=normalizedEmailAddressOrUID, customer=GC_Values[GC_CUSTOMER_ID], fields='resourceEmail') if 'resourceEmail' in result: return result['resourceEmail'].lower() - except GAPI_resourceNotFound: + except gapi.errors.GapiResourceNotFoundError: pass return normalizedEmailAddressOrUID @@ -1476,26 +1218,26 @@ def convertEmailAddressToUID(emailAddressOrUID, cd=None, email_type='user'): cd = buildGAPIObject('directory') if email_type != 'group': try: - result = callGAPI(cd.users(), 'get', - throw_reasons=[GAPI_USER_NOT_FOUND], + result = gapi.call(cd.users(), 'get', + throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND], userKey=normalizedEmailAddressOrUID, fields='id') if 'id' in result: return result['id'] - except GAPI_userNotFound: + except gapi.errors.GapiUserNotFoundError: pass try: - result = callGAPI(cd.groups(), 'get', - throw_reasons=[GAPI_NOT_FOUND], + result = gapi.call(cd.groups(), 'get', + throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND], groupKey=normalizedEmailAddressOrUID, fields='id') if 'id' in result: return result['id'] - except GAPI_notFound: + except gapi.errors.GapiNotFoundError: pass return None return normalizedEmailAddressOrUID def buildGAPIServiceObject(api, act_as, showAuthError=True): - http = _createHttpObj(cache=GM_Globals[GM_CACHE_DIR]) + http = gapi.create_http(cache=GM_Globals[GM_CACHE_DIR]) service = getService(api, http) GM_Globals[GM_CURRENT_API_USER] = act_as GM_Globals[GM_CURRENT_API_SCOPES] = API_SCOPE_MAPPING[api] @@ -1510,15 +1252,15 @@ def buildGAPIServiceObject(api, act_as, showAuthError=True): except (httplib2.ServerNotFoundError, RuntimeError) as e: if n != retries: http.connections = {} - waitOnFailure(n, retries, str(e)) + controlflow.wait_on_failure(n, retries, str(e)) continue - systemErrorExit(4, e) + controlflow.system_error_exit(4, e) except google.auth.exceptions.RefreshError as e: if isinstance(e.args, tuple): e = e.args[0] if showAuthError: - stderrErrorMsg('User {0}: {1}'.format(GM_Globals[GM_CURRENT_API_USER], str(e))) - return handleOAuthTokenError(str(e), True) + display.print_error('User {0}: {1}'.format(GM_Globals[GM_CURRENT_API_USER], str(e))) + return gapi.handle_oauth_token_error(str(e), True) return service def buildAlertCenterGAPIObject(user): @@ -1572,7 +1314,7 @@ def doCheckServiceAccount(users): check_scopes = sys.argv[i+1].replace(',', ' ').split() i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam user check serviceaccount"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam user check serviceaccount"' % myarg) something_failed = False print('Computer clock status:') timeOffset, nicetime = getLocalGoogleTimeOffset() @@ -1582,15 +1324,15 @@ def doCheckServiceAccount(users): time_status = 'FAIL' something_failed = True printPassFail(MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY % nicetime, time_status) - oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj()) + oa2 = googleapiclient.discovery.build('oauth2', 'v1', gapi.create_http()) print('Service Account Private Key Authentication:') # We are explicitly not doing DwD here, just confirming service account can auth auth_error = '' try: credentials = getSvcAcctCredentials([USERINFO_EMAIL_SCOPE], None) - request = google_auth_httplib2.Request(_createHttpObj()) + request = google_auth_httplib2.Request(gapi.create_http()) credentials.refresh(request) - sa_token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token) + sa_token_info = gapi.call(oa2, 'tokeninfo', access_token=credentials.token) if sa_token_info: sa_token_result = 'PASS' else: @@ -1610,7 +1352,7 @@ def doCheckServiceAccount(users): for user in users: user = user.lower() all_scopes_pass = True - oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj()) + oa2 = googleapiclient.discovery.build('oauth2', 'v1', gapi.create_http()) print('Domain-Wide Delegation authentication as %s:' % (user)) for scope in check_scopes: # try with and without email scope @@ -1620,11 +1362,11 @@ def doCheckServiceAccount(users): credentials.refresh(request) break except (httplib2.ServerNotFoundError, RuntimeError) as e: - systemErrorExit(4, e) + controlflow.system_error_exit(4, e) except google.auth.exceptions.RefreshError: continue if credentials.token: - token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token) + token_info = gapi.call(oa2, 'tokeninfo', access_token=credentials.token) if scope in token_info.get('scope', '').split(' ') and \ user == token_info.get('email', user).lower(): result = 'PASS' @@ -1653,7 +1395,7 @@ def doCheckServiceAccount(users): Access to scopes: %s\n''' % (user_domain, service_account, ',\n'.join(check_scopes)) - systemErrorExit(1, scopes_failed) + controlflow.system_error_exit(1, scopes_failed) # Batch processing request_id fields RI_ENTITY = 0 @@ -1670,7 +1412,7 @@ def _adjustDate(errMsg): if not match_date: match_date = re.match('Start date can not be later than (.*)', errMsg) if not match_date: - systemErrorExit(4, errMsg) + controlflow.system_error_exit(4, errMsg) return str(match_date.group(1)) def _checkFullDataAvailable(warnings, tryDate, fullDataRequired): @@ -1737,13 +1479,13 @@ def showReport(): to_drive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument to "gam report"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument to "gam report"' % sys.argv[i]) if report in ['users', 'user']: while True: try: if fullDataRequired is not None: warnings = callGAPIitems(rep.userUsageReport(), 'get', 'warnings', - throw_reasons=[GAPI_INVALID], + throw_reasons=[gapi.errors.ErrorReason.INVALID], date=tryDate, userKey=userKey, customerId=customerId, orgUnitID=orgUnitId, fields='warnings') fullData, tryDate = _checkFullDataAvailable(warnings, tryDate, fullDataRequired) if fullData < 0: @@ -1752,10 +1494,10 @@ def showReport(): if fullData == 0: continue page_message = 'Got %%total_items%% Users\n' - usage = callGAPIpages(rep.userUsageReport(), 'get', 'usageReports', page_message=page_message, throw_reasons=[GAPI_INVALID], + usage = callGAPIpages(rep.userUsageReport(), 'get', 'usageReports', page_message=page_message, throw_reasons=[gapi.errors.ErrorReason.INVALID], date=tryDate, userKey=userKey, customerId=customerId, orgUnitID=orgUnitId, filters=filters, parameters=parameters) break - except GAPI_invalid as e: + except gapi.errors.GapiInvalidError as e: tryDate = _adjustDate(str(e)) if not usage: print('No user report available.') @@ -1785,7 +1527,7 @@ def showReport(): try: if fullDataRequired is not None: warnings = callGAPIitems(rep.customerUsageReports(), 'get', 'warnings', - throw_reasons=[GAPI_INVALID], + throw_reasons=[gapi.errors.ErrorReason.INVALID], customerId=customerId, date=tryDate, fields='warnings') fullData, tryDate = _checkFullDataAvailable(warnings, tryDate, fullDataRequired) if fullData < 0: @@ -1793,10 +1535,10 @@ def showReport(): sys.exit(1) if fullData == 0: continue - usage = callGAPIpages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=[GAPI_INVALID], + usage = callGAPIpages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=[gapi.errors.ErrorReason.INVALID], customerId=customerId, date=tryDate, parameters=parameters) break - except GAPI_invalid as e: + except gapi.errors.GapiInvalidError as e: tryDate = _adjustDate(str(e)) if not usage: print('No customer report available.') @@ -1925,9 +1667,9 @@ def watchGmail(users): break else: topic = gamTopics+str(uuid.uuid4()) - callGAPI(pubsub.projects().topics(), 'create', name=topic) + gapi.call(pubsub.projects().topics(), 'create', name=topic) body = {'policy': {'bindings': [{'members': ['serviceAccount:gmail-api-push@system.gserviceaccount.com'], 'role': 'roles/pubsub.editor'}]}} - callGAPI(pubsub.projects().topics(), 'setIamPolicy', resource=topic, body=body) + gapi.call(pubsub.projects().topics(), 'setIamPolicy', resource=topic, body=body) subscriptions = callGAPIpages(pubsub.projects().topics().subscriptions(), 'list', items='subscriptions', topic=topic) for asubscription in subscriptions: if asubscription.startswith(gamSubscriptions): @@ -1935,15 +1677,15 @@ def watchGmail(users): break else: subscription = gamSubscriptions+str(uuid.uuid4()) - callGAPI(pubsub.projects().subscriptions(), 'create', name=subscription, body={'topic': topic}) + gapi.call(pubsub.projects().subscriptions(), 'create', name=subscription, body={'topic': topic}) gmails = {} for user in users: gmails[user] = {'g': buildGmailGAPIObject(user)[1]} - callGAPI(gmails[user]['g'].users(), 'watch', userId='me', body={'topicName': topic}) - gmails[user]['seen_historyId'] = callGAPI(gmails[user]['g'].users(), 'getProfile', userId='me', fields='historyId')['historyId'] + gapi.call(gmails[user]['g'].users(), 'watch', userId='me', body={'topicName': topic}) + gmails[user]['seen_historyId'] = gapi.call(gmails[user]['g'].users(), 'getProfile', userId='me', fields='historyId')['historyId'] print('Watching for events...') while True: - results = callGAPI(pubsub.projects().subscriptions(), 'pull', subscription=subscription, body={'maxMessages': 100}) + results = gapi.call(pubsub.projects().subscriptions(), 'pull', subscription=subscription, body={'maxMessages': 100}) if 'receivedMessages' in results: ackIds = [] update_history = [] @@ -1955,10 +1697,10 @@ def watchGmail(users): if 'ackId' in message: ackIds.append(message['ackId']) if ackIds: - callGAPI(pubsub.projects().subscriptions(), 'acknowledge', subscription=subscription, body={'ackIds': ackIds}) + gapi.call(pubsub.projects().subscriptions(), 'acknowledge', subscription=subscription, body={'ackIds': ackIds}) if update_history: for a_user in update_history: - results = callGAPI(gmails[a_user]['g'].users().history(), 'list', userId='me', startHistoryId=gmails[a_user]['seen_historyId']) + results = gapi.call(gmails[a_user]['g'].users().history(), 'list', userId='me', startHistoryId=gmails[a_user]['seen_historyId']) if 'history' in results: for history in results['history']: if list(history) == ['messages', 'id']: @@ -1980,7 +1722,7 @@ def watchGmail(users): def addDelegates(users, i): if i == 4: if sys.argv[i].lower() != 'to': - systemErrorExit(2, '%s is not a valid argument for "gam delegate", expected to' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam delegate", expected to' % sys.argv[i]) i += 1 delegate = normalizeEmailAddressOrUID(sys.argv[i], noUid=True) i = 0 @@ -1991,7 +1733,7 @@ def addDelegates(users, i): if not gmail: continue print("Giving %s delegate access to %s (%s/%s)" % (delegate, delegator, i, count)) - callGAPI(gmail.users().settings().delegates(), 'create', soft_errors=True, userId='me', body={'delegateEmail': delegate}) + gapi.call(gmail.users().settings().delegates(), 'create', soft_errors=True, userId='me', body={'delegateEmail': delegate}) def gen_sha512_hash(password): return sha512_crypt.hash(password, rounds=5000) @@ -2013,7 +1755,7 @@ def printShowDelegates(users, csvFormat): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show delegates"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show delegates"' % sys.argv[i]) count = len(users) i = 1 for user in users: @@ -2022,7 +1764,7 @@ def printShowDelegates(users, csvFormat): continue sys.stderr.write("Getting delegates for %s (%s/%s)...\n" % (user, i, count)) i += 1 - delegates = callGAPI(gmail.users().settings().delegates(), 'list', soft_errors=True, userId='me') + delegates = gapi.call(gmail.users().settings().delegates(), 'list', soft_errors=True, userId='me') if delegates and 'delegates' in delegates: for delegate in delegates['delegates']: delegateAddress = delegate['delegateEmail'] @@ -2050,7 +1792,7 @@ def deleteDelegate(users): if not gmail: continue print("Deleting %s delegate access to %s (%s/%s)" % (delegate, user, i, count)) - callGAPI(gmail.users().settings().delegates(), 'delete', soft_errors=True, userId='me', delegateEmail=delegate) + gapi.call(gmail.users().settings().delegates(), 'delete', soft_errors=True, userId='me', delegateEmail=delegate) def doAddCourseParticipant(): croom = buildGAPIObject('classroom') @@ -2060,18 +1802,18 @@ def doAddCourseParticipant(): new_id = sys.argv[5] if participant_type in ['student', 'students']: new_id = normalizeEmailAddressOrUID(new_id) - callGAPI(croom.courses().students(), 'create', courseId=courseId, body={'userId': new_id}) + gapi.call(croom.courses().students(), 'create', courseId=courseId, body={'userId': new_id}) print('Added %s as a student of course %s' % (new_id, noScopeCourseId)) elif participant_type in ['teacher', 'teachers']: new_id = normalizeEmailAddressOrUID(new_id) - callGAPI(croom.courses().teachers(), 'create', courseId=courseId, body={'userId': new_id}) + gapi.call(croom.courses().teachers(), 'create', courseId=courseId, body={'userId': new_id}) print('Added %s as a teacher of course %s' % (new_id, noScopeCourseId)) elif participant_type in ['alias']: new_id = addCourseIdScope(new_id) - callGAPI(croom.courses().aliases(), 'create', courseId=courseId, body={'alias': new_id}) + gapi.call(croom.courses().aliases(), 'create', courseId=courseId, body={'alias': new_id}) print('Added %s as an alias of course %s' % (removeCourseIdScope(new_id), noScopeCourseId)) else: - systemErrorExit(2, '%s is not a valid argument to "gam course ID add"' % participant_type) + controlflow.system_error_exit(2, '%s is not a valid argument to "gam course ID add"' % participant_type) def doSyncCourseParticipants(): courseId = addCourseIdScope(sys.argv[2]) @@ -2103,29 +1845,29 @@ def doDelCourseParticipant(): remove_id = sys.argv[5] if participant_type in ['student', 'students']: remove_id = normalizeEmailAddressOrUID(remove_id) - callGAPI(croom.courses().students(), 'delete', courseId=courseId, userId=remove_id) + gapi.call(croom.courses().students(), 'delete', courseId=courseId, userId=remove_id) print('Removed %s as a student of course %s' % (remove_id, noScopeCourseId)) elif participant_type in ['teacher', 'teachers']: remove_id = normalizeEmailAddressOrUID(remove_id) - callGAPI(croom.courses().teachers(), 'delete', courseId=courseId, userId=remove_id) + gapi.call(croom.courses().teachers(), 'delete', courseId=courseId, userId=remove_id) print('Removed %s as a teacher of course %s' % (remove_id, noScopeCourseId)) elif participant_type in ['alias']: remove_id = addCourseIdScope(remove_id) - callGAPI(croom.courses().aliases(), 'delete', courseId=courseId, alias=remove_id) + gapi.call(croom.courses().aliases(), 'delete', courseId=courseId, alias=remove_id) print('Removed %s as an alias of course %s' % (removeCourseIdScope(remove_id), noScopeCourseId)) else: - systemErrorExit(2, '%s is not a valid argument to "gam course ID delete"' % participant_type) + controlflow.system_error_exit(2, '%s is not a valid argument to "gam course ID delete"' % participant_type) def doDelCourse(): croom = buildGAPIObject('classroom') courseId = addCourseIdScope(sys.argv[3]) - callGAPI(croom.courses(), 'delete', id=courseId) + gapi.call(croom.courses(), 'delete', id=courseId) print('Deleted Course %s' % courseId) def _getValidatedState(state, validStates): state = state.upper() if state not in validStates: - systemErrorExit(2, 'course state must be one of: %s. Got %s' % (', '.join(validStates).lower(), state.lower())) + controlflow.system_error_exit(2, 'course state must be one of: %s. Got %s' % (', '.join(validStates).lower(), state.lower())) return state def getCourseAttribute(myarg, value, body, croom, function): @@ -2145,7 +1887,7 @@ def getCourseAttribute(myarg, value, body, croom, function): validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum']) body['courseState'] = _getValidatedState(value, validStates) else: - systemErrorExit(2, '%s is not a valid argument to "gam %s course"' % (myarg, function)) + controlflow.system_error_exit(2, '%s is not a valid argument to "gam %s course"' % (myarg, function)) def _getCourseStates(croom, value, courseStates): validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum']) @@ -2163,14 +1905,14 @@ def doUpdateCourse(): i += 2 updateMask = ','.join(list(body)) body['id'] = courseId - result = callGAPI(croom.courses(), 'patch', id=courseId, body=body, updateMask=updateMask) + result = gapi.call(croom.courses(), 'patch', id=courseId, body=body, updateMask=updateMask) print('Updated Course %s' % result['id']) def doCreateDomain(): cd = buildGAPIObject('directory') domain_name = sys.argv[3] body = {'domainName': domain_name} - callGAPI(cd.domains(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) + gapi.call(cd.domains(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) print('Added domain %s' % domain_name) def doCreateDomainAlias(): @@ -2178,7 +1920,7 @@ def doCreateDomainAlias(): body = {} body['domainAliasName'] = sys.argv[3] body['parentDomainName'] = sys.argv[4] - callGAPI(cd.domainAliases(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) + gapi.call(cd.domainAliases(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doUpdateDomain(): cd = buildGAPIObject('directory') @@ -2191,8 +1933,8 @@ def doUpdateDomain(): body['customerDomain'] = domain_name i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update domain"' % sys.argv[i]) - callGAPI(cd.customers(), 'update', customerKey=GC_Values[GC_CUSTOMER_ID], body=body) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update domain"' % sys.argv[i]) + gapi.call(cd.customers(), 'update', customerKey=GC_Values[GC_CUSTOMER_ID], body=body) print('%s is now the primary domain.' % domain_name) def doGetDomainInfo(): @@ -2201,7 +1943,7 @@ def doGetDomainInfo(): return cd = buildGAPIObject('directory') domainName = sys.argv[3] - result = callGAPI(cd.domains(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) + result = gapi.call(cd.domains(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) if 'creationTime' in result: result['creationTime'] = utils.formatTimestampYMDHMSF(result['creationTime']) if 'domainAliases' in result: @@ -2213,17 +1955,17 @@ def doGetDomainInfo(): def doGetDomainAliasInfo(): cd = buildGAPIObject('directory') alias = sys.argv[3] - result = callGAPI(cd.domainAliases(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=alias) + result = gapi.call(cd.domainAliases(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=alias) if 'creationTime' in result: result['creationTime'] = utils.formatTimestampYMDHMSF(result['creationTime']) print_json(None, result) def doGetCustomerInfo(): cd = buildGAPIObject('directory') - customer_info = callGAPI(cd.customers(), 'get', customerKey=GC_Values[GC_CUSTOMER_ID]) + customer_info = gapi.call(cd.customers(), 'get', customerKey=GC_Values[GC_CUSTOMER_ID]) print('Customer ID: %s' % customer_info['id']) print('Primary Domain: %s' % customer_info['customerDomain']) - result = callGAPI(cd.domains(), 'get', + result = gapi.call(cd.domains(), 'get', customer=customer_info['id'], domainName=customer_info['customerDomain'], fields='verified') print('Primary Domain Verified: %s' % result['verified']) # If customer has changed primary domain customerCreationTime is date @@ -2264,10 +2006,10 @@ def doGetCustomerInfo(): usage = None while True: try: - usage = callGAPIpages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=[GAPI_INVALID], + usage = callGAPIpages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=[gapi.errors.ErrorReason.INVALID], customerId=customerId, date=tryDate, parameters=parameters) break - except GAPI_invalid as e: + except gapi.errors.GapiInvalidError as e: tryDate = _adjustDate(str(e)) if not usage: print('No user count data available.') @@ -2299,21 +2041,21 @@ def doUpdateCustomer(): body['language'] = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam update customer"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update customer"' % myarg) if not body: - systemErrorExit(2, 'no arguments specified for "gam update customer"') - callGAPI(cd.customers(), 'patch', customerKey=GC_Values[GC_CUSTOMER_ID], body=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') def doDelDomain(): cd = buildGAPIObject('directory') domainName = sys.argv[3] - callGAPI(cd.domains(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) + gapi.call(cd.domains(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) def doDelDomainAlias(): cd = buildGAPIObject('directory') domainAliasName = sys.argv[3] - callGAPI(cd.domainAliases(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName) + gapi.call(cd.domainAliases(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName) def doPrintDomains(): cd = buildGAPIObject('directory') @@ -2327,8 +2069,8 @@ def doPrintDomains(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print domains".' % sys.argv[i]) - results = callGAPI(cd.domains(), 'list', customer=GC_Values[GC_CUSTOMER_ID]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print domains".' % sys.argv[i]) + results = gapi.call(cd.domains(), 'list', customer=GC_Values[GC_CUSTOMER_ID]) for domain in results['domains']: domain_attributes = {} domain['type'] = ['secondary', 'primary'][domain['isPrimary']] @@ -2370,8 +2112,8 @@ def doPrintDomainAliases(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print domainaliases".' % sys.argv[i]) - results = callGAPI(cd.domainAliases(), 'list', customer=GC_Values[GC_CUSTOMER_ID]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print domainaliases".' % sys.argv[i]) + results = gapi.call(cd.domainAliases(), 'list', customer=GC_Values[GC_CUSTOMER_ID]) for domainAlias in results['domainAliases']: domainAlias_attributes = {} for attr in domainAlias: @@ -2389,7 +2131,7 @@ def doDelAdmin(): cd = buildGAPIObject('directory') roleAssignmentId = sys.argv[3] print('Deleting Admin Role Assignment %s' % roleAssignmentId) - callGAPI(cd.roleAssignments(), 'delete', + gapi.call(cd.roleAssignments(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], roleAssignmentId=roleAssignmentId) def doCreateAdmin(): @@ -2400,7 +2142,7 @@ def doCreateAdmin(): body['roleId'] = getRoleId(role) body['scopeType'] = sys.argv[5].upper() if body['scopeType'] not in ['CUSTOMER', 'ORG_UNIT']: - systemErrorExit(3, 'scope type must be customer or org_unit; got %s' % body['scopeType']) + controlflow.system_error_exit(3, 'scope type must be customer or org_unit; got %s' % body['scopeType']) if body['scopeType'] == 'ORG_UNIT': orgUnit, orgUnitId = getOrgUnitId(sys.argv[6], cd) body['orgUnitId'] = orgUnitId[3:] @@ -2408,7 +2150,7 @@ def doCreateAdmin(): else: scope = 'CUSTOMER' print('Giving %s admin role %s for %s' % (user, role, scope)) - callGAPI(cd.roleAssignments(), 'insert', + gapi.call(cd.roleAssignments(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doPrintAdminRoles(): @@ -2424,7 +2166,7 @@ def doPrintAdminRoles(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print adminroles".' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print adminroles".' % sys.argv[i]) roles = callGAPIpages(cd.roles(), 'list', 'items', customer=GC_Values[GC_CUSTOMER_ID], fields=fields) for role in roles: @@ -2455,7 +2197,7 @@ def doPrintAdmins(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print admins".' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print admins".' % sys.argv[i]) admins = callGAPIpages(cd.roleAssignments(), 'list', 'items', customer=GC_Values[GC_CUSTOMER_ID], userKey=userKey, roleId=roleId, fields=fields) for admin in admins: @@ -2474,7 +2216,7 @@ def doPrintAdmins(): def buildOrgUnitIdToNameMap(): cd = buildGAPIObject('directory') - result = callGAPI(cd.orgunits(), 'list', + result = gapi.call(cd.orgunits(), 'list', customerId=GC_Values[GC_CUSTOMER_ID], fields='organizationUnits(orgUnitPath,orgUnitId)', type='all') GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME] = {} @@ -2514,7 +2256,7 @@ def getRoleId(role): else: roleId = roleid_from_role(role) if not roleId: - systemErrorExit(4, '%s is not a valid role. Please ensure role name is exactly as shown in admin console.' % role) + controlflow.system_error_exit(4, '%s is not a valid role. Please ensure role name is exactly as shown in admin console.' % role) return roleId def buildUserIdToNameMap(): @@ -2549,7 +2291,7 @@ def app2appID(dt, app): for online_service in online_services: if serviceName == online_service['name'].lower(): return (online_service['name'], online_service['id']) - systemErrorExit(2, '%s is not a valid service for data transfer.' % app) + controlflow.system_error_exit(2, '%s is not a valid service for data transfer.' % app) def convertToUserID(user): cg = UID_PATTERN.match(user) @@ -2559,15 +2301,15 @@ def convertToUserID(user): if user.find('@') == -1: user = '%s@%s' % (user, GC_Values[GC_DOMAIN]) try: - return callGAPI(cd.users(), 'get', throw_reasons=[GAPI_USER_NOT_FOUND, GAPI_BAD_REQUEST, GAPI_FORBIDDEN], userKey=user, fields='id')['id'] - except (GAPI_userNotFound, GAPI_badRequest, GAPI_forbidden): - systemErrorExit(3, 'no such user %s' % user) + return gapi.call(cd.users(), 'get', throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND, gapi.errors.ErrorReason.BAD_REQUEST, gapi.errors.ErrorReason.FORBIDDEN], userKey=user, fields='id')['id'] + except (gapi.errors.GapiUserNotFoundError, gapi.errors.GapiBadRequestError, gapi.errors.GapiForbiddenError): + controlflow.system_error_exit(3, 'no such user %s' % user) def convertUserIDtoEmail(uid): cd = buildGAPIObject('directory') try: - return callGAPI(cd.users(), 'get', throw_reasons=[GAPI_USER_NOT_FOUND, GAPI_BAD_REQUEST, GAPI_FORBIDDEN], userKey=uid, fields='primaryEmail')['primaryEmail'] - except (GAPI_userNotFound, GAPI_badRequest, GAPI_forbidden): + return gapi.call(cd.users(), 'get', throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND, gapi.errors.ErrorReason.BAD_REQUEST, gapi.errors.ErrorReason.FORBIDDEN], userKey=uid, fields='primaryEmail')['primaryEmail'] + except (gapi.errors.GapiUserNotFoundError, gapi.errors.GapiBadRequestError, gapi.errors.GapiForbiddenError): return 'uid:{0}'.format(uid) def doCreateDataTransfer(): @@ -2597,7 +2339,7 @@ def doCreateDataTransfer(): body['applicationDataTransfers'][i].setdefault('applicationTransferParams', []) body['applicationDataTransfers'][i]['applicationTransferParams'].append({'key': key, 'value': value}) i += 1 - result = callGAPI(dt.transfers(), 'insert', body=body, fields='id')['id'] + result = gapi.call(dt.transfers(), 'insert', body=body, fields='id')['id'] print('Submitted request id %s to transfer %s from %s to %s' % (result, ','.join(map(str, appNameList)), old_owner, new_owner)) def doPrintTransferApps(): @@ -2631,7 +2373,7 @@ def doPrintDataTransfers(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print transfers"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print transfers"' % sys.argv[i]) transfers = callGAPIpages(dt.transfers(), 'list', 'dataTransfers', customerId=GC_Values[GC_CUSTOMER_ID], status=status, newOwnerUserId=newOwnerUserId, oldOwnerUserId=oldOwnerUserId) @@ -2657,7 +2399,7 @@ def doPrintDataTransfers(): def doGetDataTransferInfo(): dt = buildGAPIObject('datatransfer') dtId = sys.argv[3] - transfer = callGAPI(dt.transfers(), 'get', dataTransferId=dtId) + transfer = gapi.call(dt.transfers(), 'get', dataTransferId=dtId) print('Old Owner: %s' % convertUserIDtoEmail(transfer['oldOwnerUserId'])) print('New Owner: %s' % convertUserIDtoEmail(transfer['newOwnerUserId'])) print('Request Time: %s' % transfer['requestTime']) @@ -2711,7 +2453,7 @@ def doPrintShowGuardians(csvFormat): studentIds = getUsersToModify(entity_type=myarg, entity=sys.argv[i+1]) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s guardians"' % (sys.argv[i], ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s guardians"' % (sys.argv[i], ['show', 'print'][csvFormat])) i = 0 count = len(studentIds) for studentId in studentIds: @@ -2742,24 +2484,24 @@ def doInviteGuardian(): croom = buildGAPIObject('classroom') body = {'invitedEmailAddress': normalizeEmailAddressOrUID(sys.argv[3])} studentId = normalizeStudentGuardianEmailAddressOrUID(sys.argv[4]) - result = callGAPI(croom.userProfiles().guardianInvitations(), 'create', studentId=studentId, body=body) + result = gapi.call(croom.userProfiles().guardianInvitations(), 'create', studentId=studentId, body=body) print('Invited email %s as guardian of %s. Invite ID %s' % (result['invitedEmailAddress'], studentId, result['invitationId'])) def _cancelGuardianInvitation(croom, studentId, invitationId): try: - result = callGAPI(croom.userProfiles().guardianInvitations(), 'patch', - throw_reasons=[GAPI_FAILED_PRECONDITION, GAPI_FORBIDDEN, GAPI_NOT_FOUND], + result = gapi.call(croom.userProfiles().guardianInvitations(), 'patch', + throw_reasons=[gapi.errors.ErrorReason.FAILED_PRECONDITION, gapi.errors.ErrorReason.FORBIDDEN, gapi.errors.ErrorReason.NOT_FOUND], studentId=studentId, invitationId=invitationId, updateMask='state', body={'state': 'COMPLETE'}) print('Cancelled PENDING guardian invitation for %s as guardian of %s' % (result['invitedEmailAddress'], studentId)) return True - except GAPI_failedPrecondition: - stderrErrorMsg('Guardian invitation %s for %s status is not PENDING' % (invitationId, studentId)) + except gapi.errors.GapiFailedPreconditionError: + display.print_error('Guardian invitation %s for %s status is not PENDING' % (invitationId, studentId)) GM_Globals[GM_SYSEXITRC] = 3 return True - except GAPI_forbidden: + except gapi.errors.GapiForbiddenError: entityUnknownWarning('Student', studentId, 0, 0) sys.exit(3) - except GAPI_notFound: + except gapi.errors.GapiNotFoundError: return False def doCancelGuardianInvitation(): @@ -2767,19 +2509,19 @@ def doCancelGuardianInvitation(): invitationId = sys.argv[3] studentId = normalizeStudentGuardianEmailAddressOrUID(sys.argv[4]) if not _cancelGuardianInvitation(croom, studentId, invitationId): - systemErrorExit(3, 'Guardian invitation %s for %s does not exist' % (invitationId, studentId)) + controlflow.system_error_exit(3, 'Guardian invitation %s for %s does not exist' % (invitationId, studentId)) def _deleteGuardian(croom, studentId, guardianId, guardianEmail): try: - callGAPI(croom.userProfiles().guardians(), 'delete', - throw_reasons=[GAPI_FORBIDDEN, GAPI_NOT_FOUND], + gapi.call(croom.userProfiles().guardians(), 'delete', + throw_reasons=[gapi.errors.ErrorReason.FORBIDDEN, gapi.errors.ErrorReason.NOT_FOUND], studentId=studentId, guardianId=guardianId) print('Deleted %s as a guardian of %s' % (guardianEmail, studentId)) return True - except GAPI_forbidden: + except gapi.errors.GapiForbiddenError: entityUnknownWarning('Student', studentId, 0, 0) sys.exit(3) - except GAPI_notFound: + except gapi.errors.GapiNotFoundError: return False def doDeleteGuardian(): @@ -2795,19 +2537,19 @@ def doDeleteGuardian(): invitationsOnly = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam delete guardian"' % (sys.argv[i])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam delete guardian"' % (sys.argv[i])) if not invitationsOnly: if guardianIdIsEmail: try: results = callGAPIpages(croom.userProfiles().guardians(), 'list', 'guardians', - throw_reasons=[GAPI_FORBIDDEN], + throw_reasons=[gapi.errors.ErrorReason.FORBIDDEN], studentId=studentId, invitedEmailAddress=guardianId, fields='nextPageToken,guardians(studentId,guardianId)') if results: for result in results: _deleteGuardian(croom, result['studentId'], result['guardianId'], guardianId) return - except GAPI_forbidden: + except gapi.errors.GapiForbiddenError: entityUnknownWarning('Student', studentId, 0, 0) sys.exit(3) else: @@ -2817,20 +2559,20 @@ def doDeleteGuardian(): if guardianIdIsEmail: try: results = callGAPIpages(croom.userProfiles().guardianInvitations(), 'list', 'guardianInvitations', - throw_reasons=[GAPI_FORBIDDEN], + throw_reasons=[gapi.errors.ErrorReason.FORBIDDEN], studentId=studentId, invitedEmailAddress=guardianId, states=['PENDING',], fields='nextPageToken,guardianInvitations(studentId,invitationId)') if results: for result in results: status = _cancelGuardianInvitation(croom, result['studentId'], result['invitationId']) sys.exit(status) - except GAPI_forbidden: + except gapi.errors.GapiForbiddenError: entityUnknownWarning('Student', studentId, 0, 0) sys.exit(3) else: if _cancelGuardianInvitation(croom, studentId, guardianId): return - systemErrorExit(3, '%s is not a guardian of %s and no invitation exists.' % (guardianId, studentId)) + controlflow.system_error_exit(3, '%s is not a guardian of %s and no invitation exists.' % (guardianId, studentId)) def doCreateCourse(): croom = buildGAPIObject('classroom') @@ -2845,23 +2587,23 @@ def doCreateCourse(): getCourseAttribute(myarg, sys.argv[i+1], body, croom, 'create') i += 2 if 'ownerId' not in body: - systemErrorExit(2, 'expected teacher )') + controlflow.system_error_exit(2, 'expected teacher )') if 'name' not in body: - systemErrorExit(2, 'expected name )') - result = callGAPI(croom.courses(), 'create', body=body) + controlflow.system_error_exit(2, 'expected name )') + result = gapi.call(croom.courses(), 'create', body=body) print('Created course %s' % result['id']) def doGetCourseInfo(): croom = buildGAPIObject('classroom') courseId = addCourseIdScope(sys.argv[3]) - info = callGAPI(croom.courses(), 'get', id=courseId) + info = gapi.call(croom.courses(), 'get', id=courseId) info['ownerEmail'] = convertUIDtoEmailAddress('uid:%s' % info['ownerId']) print_json(None, info) teachers = callGAPIpages(croom.courses().teachers(), 'list', 'teachers', courseId=courseId) students = callGAPIpages(croom.courses().students(), 'list', 'students', courseId=courseId) try: - aliases = callGAPIpages(croom.courses().aliases(), 'list', 'aliases', throw_reasons=[GAPI_NOT_IMPLEMENTED], courseId=courseId) - except GAPI_notImplemented: + aliases = callGAPIpages(croom.courses().aliases(), 'list', 'aliases', throw_reasons=[gapi.errors.ErrorReason.NOT_IMPLEMENTED], courseId=courseId) + except gapi.errors.GapiNotImplementedError: aliases = [] if aliases: print('Aliases:') @@ -2910,7 +2652,7 @@ def _processFieldsList(myarg, i, fList): if field != 'id': fList.append(COURSE_ARGUMENT_TO_PROPERTY_MAP[field]) else: - systemErrorExit(2, '%s is not a valid argument for "gam print courses %s"' % (field, myarg)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print courses %s"' % (field, myarg)) def _saveParticipants(course, participants, role): jcount = len(participants) @@ -2979,7 +2721,7 @@ def _saveParticipants(course, participants, role): elif myarg == 'show': showMembers = sys.argv[i+1].lower() if showMembers not in ['all', 'students', 'teachers']: - systemErrorExit(2, 'show must be all, students or teachers; got %s' % showMembers) + controlflow.system_error_exit(2, 'show must be all, students or teachers; got %s' % showMembers) i += 2 elif myarg == 'fields': if not fieldsList: @@ -2994,7 +2736,7 @@ def _saveParticipants(course, participants, role): cd = buildGAPIObject('directory') i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print courses"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print courses"' % sys.argv[i]) if ownerEmails is not None and fieldsList: fieldsList.append('ownerId') fields = 'nextPageToken,courses({0})'.format(','.join(set(fieldsList))) if fieldsList else None @@ -3078,10 +2820,10 @@ def doPrintCourseParticipants(): elif myarg == 'show': showMembers = sys.argv[i+1].lower() if showMembers not in ['all', 'students', 'teachers']: - systemErrorExit(2, 'show must be all, students or teachers; got %s' % showMembers) + controlflow.system_error_exit(2, 'show must be all, students or teachers; got %s' % showMembers) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print course-participants"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print course-participants"' % sys.argv[i]) if not courses: printGettingAllItems('Courses', None) page_message = 'Got %%total_items%% Courses...\n' @@ -3090,7 +2832,7 @@ def doPrintCourseParticipants(): else: all_courses = [] for course in courses: - all_courses.append(callGAPI(croom.courses(), 'get', id=course, fields='id,name')) + all_courses.append(gapi.call(croom.courses(), 'get', id=course, fields='id,name')) i = 0 count = len(all_courses) for course in all_courses: @@ -3136,7 +2878,7 @@ def doPrintPrintJobs(): older_or_newer = 'newer' age_number = sys.argv[i+1][:-1] if not age_number.isdigit(): - systemErrorExit(2, 'expected a number; got %s' % age_number) + controlflow.system_error_exit(2, 'expected a number; got %s' % age_number) age_unit = sys.argv[i+1][-1].lower() if age_unit == 'm': age = int(time.time()) - (int(age_number) * 60) @@ -3145,7 +2887,7 @@ def doPrintPrintJobs(): elif age_unit == 'd': age = int(time.time()) - (int(age_number) * 60 * 60 * 24) else: - systemErrorExit(2, 'expected m (minutes), h (hours) or d (days); got %s' % age_unit) + controlflow.system_error_exit(2, 'expected m (minutes), h (hours) or d (days); got %s' % age_unit) i += 2 elif myarg == 'query': query = sys.argv[i+1] @@ -3162,7 +2904,7 @@ def doPrintPrintJobs(): elif myarg == 'orderby': sortorder = sys.argv[i+1].lower().replace('_', '') if sortorder not in PRINTJOB_ASCENDINGORDER_MAP: - systemErrorExit(2, 'orderby must be one of %s; got %s' % (', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder)) + controlflow.system_error_exit(2, 'orderby must be one of %s; got %s' % (', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder)) sortorder = PRINTJOB_ASCENDINGORDER_MAP[sortorder] i += 2 elif myarg in ['printer', 'printerid']: @@ -3175,11 +2917,11 @@ def doPrintPrintJobs(): jobLimit = getInteger(sys.argv[i+1], myarg, minVal=0) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print printjobs"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print printjobs"' % sys.argv[i]) if sortorder and descending: sortorder = PRINTJOB_DESCENDINGORDER_MAP[sortorder] if printerid: - result = callGAPI(cp.printers(), 'get', + result = gapi.call(cp.printers(), 'get', printerid=printerid) checkCloudPrintResult(result) if ((not sortorder) or (sortorder == 'CREATE_TIME_DESC')) and (older_or_newer == 'newer'): @@ -3196,7 +2938,7 @@ def doPrintPrintJobs(): limit = min(PRINTJOBS_DEFAULT_MAX_RESULTS, jobLimit-jobCount) if limit == 0: break - result = callGAPI(cp.jobs(), 'list', + result = gapi.call(cp.jobs(), 'list', printerid=printerid, q=query, status=status, sortorder=sortorder, owner=owner, offset=offset, limit=limit) checkCloudPrintResult(result) @@ -3257,9 +2999,9 @@ def doPrintPrinters(): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print printers"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print printers"' % sys.argv[i]) for query in queries: - printers = callGAPI(cp.printers(), 'list', q=query, type=printer_type, connection_status=connection_status, extra_fields=extra_fields) + printers = gapi.call(cp.printers(), 'list', q=query, type=printer_type, connection_status=connection_status, extra_fields=extra_fields) checkCloudPrintResult(printers) for printer in printers['printers']: printer['createTime'] = utils.formatTimestampYMDHMS(printer['createTime']) @@ -3292,7 +3034,7 @@ def changeCalendarAttendees(users): allevents = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update calattendees"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update calattendees"' % sys.argv[i]) attendee_map = {} f = openFile(csv_file) csvFile = csv.reader(f) @@ -3306,7 +3048,7 @@ def changeCalendarAttendees(users): continue page_token = None while True: - events_page = callGAPI(cal.events(), 'list', calendarId=user, + events_page = gapi.call(cal.events(), 'list', calendarId=user, pageToken=page_token, timeMin=start_date, timeMax=end_date, showDeleted=False, showHiddenInvitations=False) @@ -3345,7 +3087,7 @@ def changeCalendarAttendees(users): body['attendees'] = event['attendees'] print('UPDATING %s' % event_summary) if do_it: - callGAPI(cal.events(), 'patch', calendarId=user, eventId=event['id'], sendNotifications=False, body=body) + gapi.call(cal.events(), 'patch', calendarId=user, eventId=event['id'], sendNotifications=False, body=body) else: print(' not pulling the trigger.') #else: @@ -3361,7 +3103,7 @@ def deleteCalendar(users): user, cal = buildCalendarGAPIObject(user) if not cal: continue - callGAPI(cal.calendarList(), 'delete', soft_errors=True, calendarId=calendarId) + gapi.call(cal.calendarList(), 'delete', soft_errors=True, calendarId=calendarId) CALENDAR_REMINDER_MAX_MINUTES = 40320 @@ -3400,7 +3142,7 @@ def getCalendarAttributes(i, body, function): method = sys.argv[i+1].lower() if method not in CLEAR_NONE_ARGUMENT: if method not in CALENDAR_REMINDER_METHODS: - systemErrorExit(2, 'Method must be one of %s; got %s' % (', '.join(CALENDAR_REMINDER_METHODS+CLEAR_NONE_ARGUMENT), method)) + controlflow.system_error_exit(2, 'Method must be one of %s; got %s' % (', '.join(CALENDAR_REMINDER_METHODS+CLEAR_NONE_ARGUMENT), method)) minutes = getInteger(sys.argv[i+2], myarg, minVal=0, maxVal=CALENDAR_REMINDER_MAX_MINUTES) body['defaultReminders'].append({'method': method, 'minutes': minutes}) i += 3 @@ -3411,16 +3153,16 @@ def getCalendarAttributes(i, body, function): method = sys.argv[i+1].lower() if method not in CLEAR_NONE_ARGUMENT: if method not in CALENDAR_NOTIFICATION_METHODS: - systemErrorExit(2, 'Method must be one of %s; got %s' % (', '.join(CALENDAR_NOTIFICATION_METHODS+CLEAR_NONE_ARGUMENT), method)) + controlflow.system_error_exit(2, 'Method must be one of %s; got %s' % (', '.join(CALENDAR_NOTIFICATION_METHODS+CLEAR_NONE_ARGUMENT), method)) eventType = sys.argv[i+2].lower() if eventType not in CALENDAR_NOTIFICATION_TYPES_MAP: - systemErrorExit(2, 'Event must be one of %s; got %s' % (', '.join(CALENDAR_NOTIFICATION_TYPES_MAP), eventType)) + controlflow.system_error_exit(2, 'Event must be one of %s; got %s' % (', '.join(CALENDAR_NOTIFICATION_TYPES_MAP), eventType)) body['notificationSettings']['notifications'].append({'method': method, 'type': CALENDAR_NOTIFICATION_TYPES_MAP[eventType]}) i += 3 else: i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s calendar"' % (sys.argv[i], function)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s calendar"' % (sys.argv[i], function)) return colorRgbFormat def addCalendar(users): @@ -3435,7 +3177,7 @@ def addCalendar(users): if not cal: continue print("Subscribing %s to %s calendar (%s/%s)" % (user, calendarId, i, count)) - callGAPI(cal.calendarList(), 'insert', soft_errors=True, body=body, colorRgbFormat=colorRgbFormat) + gapi.call(cal.calendarList(), 'insert', soft_errors=True, body=body, colorRgbFormat=colorRgbFormat) def updateCalendar(users): calendarId = normalizeCalendarId(sys.argv[5], checkPrimary=True) @@ -3450,12 +3192,12 @@ def updateCalendar(users): continue print("Updating %s's subscription to calendar %s (%s/%s)" % (user, calendarId, i, count)) calId = calendarId if calendarId != 'primary' else user - callGAPI(cal.calendarList(), 'patch', soft_errors=True, calendarId=calId, body=body, colorRgbFormat=colorRgbFormat) + gapi.call(cal.calendarList(), 'patch', soft_errors=True, calendarId=calId, body=body, colorRgbFormat=colorRgbFormat) def doPrinterShowACL(): cp = buildGAPIObject('cloudprint') show_printer = sys.argv[2] - printer_info = callGAPI(cp.printers(), 'get', printerid=show_printer) + printer_info = gapi.call(cp.printers(), 'get', printerid=show_printer) checkCloudPrintResult(printer_info) for acl in printer_info['printers'][0]['access']: if 'key' in acl: @@ -3480,7 +3222,7 @@ def doPrinterAddACL(): scope = '/hd/domain/%s' % scope else: skip_notification = not notify - result = callGAPI(cp.printers(), 'share', printerid=printer, role=role, scope=scope, public=public, skip_notification=skip_notification) + result = gapi.call(cp.printers(), 'share', printerid=printer, role=role, scope=scope, public=public, skip_notification=skip_notification) checkCloudPrintResult(result) who = scope if who is None: @@ -3498,7 +3240,7 @@ def doPrinterDelACL(): scope = None elif scope.find('@') == -1: scope = '/hd/domain/%s' % scope - result = callGAPI(cp.printers(), 'unshare', printerid=printer, scope=scope, public=public) + result = gapi.call(cp.printers(), 'unshare', printerid=printer, scope=scope, public=public) checkCloudPrintResult(result) who = scope if who is None: @@ -3566,7 +3308,7 @@ def doPrintJobFetch(): older_or_newer = 'newer' age_number = sys.argv[i+1][:-1] if not age_number.isdigit(): - systemErrorExit(2, 'expected a number; got %s' % age_number) + controlflow.system_error_exit(2, 'expected a number; got %s' % age_number) age_unit = sys.argv[i+1][-1].lower() if age_unit == 'm': age = int(time.time()) - (int(age_number) * 60) @@ -3575,7 +3317,7 @@ def doPrintJobFetch(): elif age_unit == 'd': age = int(time.time()) - (int(age_number) * 60 * 60 * 24) else: - systemErrorExit(2, 'expected m (minutes), h (hours) or d (days); got %s' % age_unit) + controlflow.system_error_exit(2, 'expected m (minutes), h (hours) or d (days); got %s' % age_unit) i += 2 elif myarg == 'query': query = sys.argv[i+1] @@ -3592,7 +3334,7 @@ def doPrintJobFetch(): elif myarg == 'orderby': sortorder = sys.argv[i+1].lower().replace('_', '') if sortorder not in PRINTJOB_ASCENDINGORDER_MAP: - systemErrorExit(2, 'orderby must be one of %s; got %s' % (', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder)) + controlflow.system_error_exit(2, 'orderby must be one of %s; got %s' % (', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder)) sortorder = PRINTJOB_ASCENDINGORDER_MAP[sortorder] i += 2 elif myarg in ['owner', 'user']: @@ -3610,11 +3352,11 @@ def doPrintJobFetch(): os.makedirs(targetFolder) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam printjobs fetch"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam printjobs fetch"' % sys.argv[i]) if sortorder and descending: sortorder = PRINTJOB_DESCENDINGORDER_MAP[sortorder] if printerid: - result = callGAPI(cp.printers(), 'get', + result = gapi.call(cp.printers(), 'get', printerid=printerid) checkCloudPrintResult(result) ssd = '{"state": {"type": "DONE"}}' @@ -3632,7 +3374,7 @@ def doPrintJobFetch(): limit = min(PRINTJOBS_DEFAULT_MAX_RESULTS, jobLimit-jobCount) if limit == 0: break - result = callGAPI(cp.jobs(), 'list', + result = gapi.call(cp.jobs(), 'list', printerid=printerid, q=query, status=status, sortorder=sortorder, owner=owner, offset=offset, limit=limit) checkCloudPrintResult(result) @@ -3660,8 +3402,8 @@ def doPrintJobFetch(): fileName = os.path.join(targetFolder, '{0}-{1}'.format(''.join(c if c in FILENAME_SAFE_CHARS else '_' for c in job['title']), jobid)) _, content = cp._http.request(uri=fileUrl, method='GET') if writeFile(fileName, content, mode='wb', continueOnError=True): -# ticket = callGAPI(cp.jobs(), u'getticket', jobid=jobid, use_cjt=True) - result = callGAPI(cp.jobs(), 'update', jobid=jobid, semantic_state_diff=ssd) +# ticket = gapi.call(cp.jobs(), u'getticket', jobid=jobid, use_cjt=True) + result = gapi.call(cp.jobs(), 'update', jobid=jobid, semantic_state_diff=ssd) checkCloudPrintResult(result) print('Printed job %s to %s' % (jobid, fileName)) if jobCount >= totalJobs: @@ -3672,7 +3414,7 @@ def doPrintJobFetch(): def doDelPrinter(): cp = buildGAPIObject('cloudprint') printerid = sys.argv[3] - result = callGAPI(cp.printers(), 'delete', printerid=printerid) + result = gapi.call(cp.printers(), 'delete', printerid=printerid) checkCloudPrintResult(result) def doGetPrinterInfo(): @@ -3686,8 +3428,8 @@ def doGetPrinterInfo(): everything = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam info printer"' % sys.argv[i]) - result = callGAPI(cp.printers(), 'get', printerid=printerid) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info printer"' % sys.argv[i]) + result = gapi.call(cp.printers(), 'get', printerid=printerid) checkCloudPrintResult(result) printer_info = result['printers'][0] printer_info['createTime'] = utils.formatTimestampYMDHMS(printer_info['createTime']) @@ -3718,8 +3460,8 @@ def doUpdatePrinter(): arg_in_item = True break if not arg_in_item: - systemErrorExit(2, '%s is not a valid argument for "gam update printer"' % sys.argv[i]) - result = callGAPI(cp.printers(), 'update', printerid=printerid, **kwargs) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update printer"' % sys.argv[i]) + result = gapi.call(cp.printers(), 'update', printerid=printerid, **kwargs) checkCloudPrintResult(result) print('Updated printer %s' % printerid) @@ -3754,7 +3496,7 @@ def doPrinterRegister(): } body, headers = encode_multipart(form_fields, {}) #Get the printer first to make sure our OAuth access token is fresh - callGAPI(cp.printers(), 'list') + gapi.call(cp.printers(), 'list') _, result = cp._http.request(uri='https://www.google.com/cloudprint/register', method='POST', body=body, headers=headers) result = json.loads(result.decode(UTF8)) checkCloudPrintResult(result) @@ -3765,10 +3507,10 @@ def doPrintJobResubmit(): jobid = sys.argv[2] printerid = sys.argv[4] ssd = '{"state": {"type": "HELD"}}' - result = callGAPI(cp.jobs(), 'update', jobid=jobid, semantic_state_diff=ssd) + result = gapi.call(cp.jobs(), 'update', jobid=jobid, semantic_state_diff=ssd) checkCloudPrintResult(result) - ticket = callGAPI(cp.jobs(), 'getticket', jobid=jobid, use_cjt=True) - result = callGAPI(cp.jobs(), 'resubmit', printerid=printerid, jobid=jobid, ticket=ticket) + ticket = gapi.call(cp.jobs(), 'getticket', jobid=jobid, use_cjt=True) + result = gapi.call(cp.jobs(), 'resubmit', printerid=printerid, jobid=jobid, ticket=ticket) checkCloudPrintResult(result) print('Success resubmitting %s as job %s to printer %s' % (jobid, result['job']['id'], printerid)) @@ -3790,7 +3532,7 @@ def doPrintJobSubmit(): form_fields['title'] = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam printer ... print"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam printer ... print"' % sys.argv[i]) form_files = {} if content[:4] == 'http': form_fields['content'] = content @@ -3803,10 +3545,10 @@ def doPrintJobSubmit(): mimetype = 'application/octet-stream' filecontent = readFile(filepath, mode='rb') form_files['content'] = {'filename': content, 'content': filecontent, 'mimetype': mimetype} - #result = callGAPI(cp.printers(), u'submit', body=body) + #result = gapi.call(cp.printers(), u'submit', body=body) body, headers = encode_multipart(form_fields, form_files) #Get the printer first to make sure our OAuth access token is fresh - callGAPI(cp.printers(), 'get', printerid=printer) + gapi.call(cp.printers(), 'get', printerid=printer) _, result = cp._http.request(uri='https://www.google.com/cloudprint/submit', method='POST', body=body, headers=headers) result = json.loads(result.decode(UTF8)) checkCloudPrintResult(result) @@ -3815,7 +3557,7 @@ def doPrintJobSubmit(): def doDeletePrintJob(): cp = buildGAPIObject('cloudprint') job = sys.argv[2] - result = callGAPI(cp.jobs(), 'delete', jobid=job) + result = gapi.call(cp.jobs(), 'delete', jobid=job) checkCloudPrintResult(result) print('Print Job %s deleted' % job) @@ -3823,7 +3565,7 @@ def doCancelPrintJob(): cp = buildGAPIObject('cloudprint') job = sys.argv[2] ssd = '{"state": {"type": "ABORTED", "user_action_cause": {"action_code": "CANCELLED"}}}' - result = callGAPI(cp.jobs(), 'update', jobid=job, semantic_state_diff=ssd) + result = gapi.call(cp.jobs(), 'update', jobid=job, semantic_state_diff=ssd) checkCloudPrintResult(result) print('Print Job %s cancelled' % job) @@ -3834,9 +3576,9 @@ def checkCloudPrintResult(result): try: result = json.loads(result) except ValueError: - systemErrorExit(3, 'unexpected response: %s' % result) + controlflow.system_error_exit(3, 'unexpected response: %s' % result) if not result['success']: - systemErrorExit(result['errorCode'], '%s: %s' % (result['errorCode'], result['message'])) + controlflow.system_error_exit(result['errorCode'], '%s: %s' % (result['errorCode'], result['message'])) def formatACLScope(rule): if rule['scope']['type'] != 'default': @@ -3860,7 +3602,7 @@ def doCalendarPrintShowACLs(csvFormat): toDrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar %s"' % (sys.argv[i], ['showacl', 'printacl'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar %s"' % (sys.argv[i], ['showacl', 'printacl'][csvFormat])) acls = callGAPIpages(cal.acl(), 'list', 'items', calendarId=calendarId) i = 0 if csvFormat: @@ -3917,7 +3659,7 @@ def doCalendarAddACL(function): return myarg = sys.argv[4].lower().replace('_', '') if myarg not in CALENDAR_ACL_ROLES_MAP: - systemErrorExit(2, 'Role must be one of %s; got %s' % (', '.join(sorted(CALENDAR_ACL_ROLES_MAP)), myarg)) + controlflow.system_error_exit(2, 'Role must be one of %s; got %s' % (', '.join(sorted(CALENDAR_ACL_ROLES_MAP)), myarg)) body = {'role': CALENDAR_ACL_ROLES_MAP[myarg]} i = _getCalendarACLScope(5, body) sendNotifications = True @@ -3927,9 +3669,9 @@ def doCalendarAddACL(function): sendNotifications = getBoolean(sys.argv[i+1], myarg) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar %s"' % (sys.argv[i], function.lower())) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar %s"' % (sys.argv[i], function.lower())) print('Calendar: {0}, {1} ACL: {2}'.format(calendarId, function, formatACLRule(body))) - callGAPI(cal.acl(), 'insert', calendarId=calendarId, body=body, sendNotifications=sendNotifications) + gapi.call(cal.acl(), 'insert', calendarId=calendarId, body=body, sendNotifications=sendNotifications) def doCalendarDelACL(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) @@ -3938,18 +3680,18 @@ def doCalendarDelACL(): if sys.argv[4].lower() == 'id': ruleId = sys.argv[5] print('Removing rights for %s to %s' % (ruleId, calendarId)) - callGAPI(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId) + gapi.call(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId) else: body = {'role': 'none'} _getCalendarACLScope(5, body) print('Calendar: {0}, {1} ACL: {2}'.format(calendarId, 'Delete', formatACLScope(body))) - callGAPI(cal.acl(), 'insert', calendarId=calendarId, body=body, sendNotifications=False) + gapi.call(cal.acl(), 'insert', calendarId=calendarId, body=body, sendNotifications=False) def doCalendarWipeData(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) if not cal: return - callGAPI(cal.calendars(), 'clear', calendarId=calendarId) + gapi.call(cal.calendars(), 'clear', calendarId=calendarId) def doCalendarPrintEvents(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) @@ -3987,7 +3729,7 @@ def doCalendarPrintEvents(): toDrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar printevents"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar printevents"' % sys.argv[i]) page_message = 'Got %%%%total_items%%%% events for %s' % calendarId results = callGAPIpages(cal.events(), 'list', 'items', page_message=page_message, calendarId=calendarId, q=q, showDeleted=showDeleted, @@ -4012,7 +3754,7 @@ def getSendUpdates(myarg, i, cal): sendUpdatesMap[val.lower()] = val sendUpdates = sendUpdatesMap.get(sys.argv[i+1].lower(), False) if not sendUpdates: - systemErrorExit(3, 'sendupdates must be one of: %s. Got %s' % (', '.join(sendUpdatesMap), sys.argv[i+1])) + controlflow.system_error_exit(3, 'sendupdates must be one of: %s. Got %s' % (', '.join(sendUpdatesMap), sys.argv[i+1])) i += 2 return (sendUpdates, i) @@ -4032,7 +3774,7 @@ def doCalendarMoveOrDeleteEvent(moveOrDelete): eventId = sys.argv[i+1] i += 2 elif myarg in ['query', 'eventquery']: - systemErrorExit(2, 'query is no longer supported for {0}event. Use "gam calendar printevents query | gam csv - gam {0}event id ~id" instead.'.format(moveOrDelete)) + controlflow.system_error_exit(2, 'query is no longer supported for {0}event. Use "gam calendar printevents query | gam csv - gam {0}event id ~id" instead.'.format(moveOrDelete)) elif myarg == 'doit': doit = True i += 1 @@ -4040,10 +3782,10 @@ def doCalendarMoveOrDeleteEvent(moveOrDelete): kwargs['destination'] = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar %sevent"' % (sys.argv[i], moveOrDelete)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar %sevent"' % (sys.argv[i], moveOrDelete)) if doit: print(' going to %s eventId %s' % (moveOrDelete, eventId)) - callGAPI(cal.events(), moveOrDelete, calendarId=calendarId, eventId=eventId, sendUpdates=sendUpdates, **kwargs) + gapi.call(cal.events(), moveOrDelete, calendarId=calendarId, eventId=eventId, sendUpdates=sendUpdates, **kwargs) else: print(' would {0} eventId {1}. Add doit to command to actually {0} event'.format(moveOrDelete, eventId)) @@ -4108,7 +3850,7 @@ def doCalendarAddEvent(): if sys.argv[i+1].lower() in ['default', 'public', 'private']: body['visibility'] = sys.argv[i+1].lower() else: - systemErrorExit(2, 'visibility must be one of default, public, private; got %s' % sys.argv[i+1]) + controlflow.system_error_exit(2, 'visibility must be one of default, public, private; got %s' % sys.argv[i+1]) i += 2 elif myarg == 'tentative': body['status'] = 'tentative' @@ -4145,15 +3887,15 @@ def doCalendarAddEvent(): body['colorId'] = getInteger(sys.argv[i+1], myarg, CALENDAR_EVENT_MIN_COLOR_INDEX, CALENDAR_EVENT_MAX_COLOR_INDEX) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar addevent"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar addevent"' % sys.argv[i]) if ('recurrence' in body) and (('start' in body) or ('end' in body)): if not timeZone: - timeZone = callGAPI(cal.calendars(), 'get', calendarId=calendarId, fields='timeZone')['timeZone'] + timeZone = gapi.call(cal.calendars(), 'get', calendarId=calendarId, fields='timeZone')['timeZone'] if 'start' in body: body['start']['timeZone'] = timeZone if 'end' in body: body['end']['timeZone'] = timeZone - callGAPI(cal.events(), 'insert', calendarId=calendarId, sendUpdates=sendUpdates, body=body) + gapi.call(cal.events(), 'insert', calendarId=calendarId, sendUpdates=sendUpdates, body=body) def doCalendarModifySettings(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) @@ -4176,8 +3918,8 @@ def doCalendarModifySettings(): body['timeZone'] = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar modify"' % sys.argv[i]) - callGAPI(cal.calendars(), 'patch', calendarId=calendarId, body=body) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar modify"' % sys.argv[i]) + gapi.call(cal.calendars(), 'patch', calendarId=calendarId, body=body) def doProfile(users): cd = buildGAPIObject('directory') @@ -4187,13 +3929,13 @@ def doProfile(users): elif myarg in ['unshare', 'unshared']: body = {'includeInGlobalAddressList': False} else: - systemErrorExit(2, 'value for "gam profile" must be true or false; got %s' % sys.argv[4]) + controlflow.system_error_exit(2, 'value for "gam profile" must be true or false; got %s' % sys.argv[4]) i = 0 count = len(users) for user in users: i += 1 print('Setting Profile Sharing to %s for %s (%s/%s)' % (body['includeInGlobalAddressList'], user, i, count)) - callGAPI(cd.users(), 'update', soft_errors=True, userKey=user, body=body) + gapi.call(cd.users(), 'update', soft_errors=True, userKey=user, body=body) def showProfile(users): cd = buildGAPIObject('directory') @@ -4201,7 +3943,7 @@ def showProfile(users): count = len(users) for user in users: i += 1 - result = callGAPI(cd.users(), 'get', userKey=user, fields='includeInGlobalAddressList') + result = gapi.call(cd.users(), 'get', userKey=user, fields='includeInGlobalAddressList') try: print('User: %s Profile Shared: %s (%s/%s)' % (user, result['includeInGlobalAddressList'], i, count)) except IndexError: @@ -4218,7 +3960,7 @@ def doPhoto(users): filename = filename.replace('#username#', user[:user.find('@')]) print("Updating photo for %s with %s (%s/%s)" % (user, filename, i, count)) if re.match('^(ht|f)tps?://.*$', filename): - simplehttp = _createHttpObj() + simplehttp = gapi.create_http() try: (_, image_data) = simplehttp.request(filename, 'GET') except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError) as e: @@ -4229,7 +3971,7 @@ def doPhoto(users): if image_data is None: continue body = {'photoData': base64.urlsafe_b64encode(image_data).decode(UTF8)} - callGAPI(cd.users().photos(), 'update', soft_errors=True, userKey=user, body=body) + gapi.call(cd.users().photos(), 'update', soft_errors=True, userKey=user, body=body) def getPhoto(users): cd = buildGAPIObject('directory') @@ -4250,7 +3992,7 @@ def getPhoto(users): showPhotoData = False i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam get photo"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam get photo"' % sys.argv[i]) i = 0 count = len(users) for user in users: @@ -4258,11 +4000,11 @@ def getPhoto(users): filename = os.path.join(targetFolder, '{0}.jpg'.format(user)) print("Saving photo to %s (%s/%s)" % (filename, i, count)) try: - photo = callGAPI(cd.users().photos(), 'get', throw_reasons=[GAPI_USER_NOT_FOUND, GAPI_RESOURCE_NOT_FOUND], userKey=user) - except GAPI_userNotFound: + photo = gapi.call(cd.users().photos(), 'get', throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND, gapi.errors.ErrorReason.RESOURCE_NOT_FOUND], userKey=user) + except gapi.errors.gapi.errors.GapiUserNotFoundError: print(' unknown user %s' % user) continue - except GAPI_resourceNotFound: + except gapi.errors.gapi.errors.GapiResourceNotFoundError: print(' no photo for %s' % user) continue try: @@ -4282,7 +4024,7 @@ def deletePhoto(users): for user in users: i += 1 print("Deleting photo for %s (%s/%s)" % (user, i, count)) - callGAPI(cd.users().photos(), 'delete', userKey=user) + gapi.call(cd.users().photos(), 'delete', userKey=user) def _showCalendar(userCalendar, j, jcount): print(' Calendar: {0} ({1}/{2})'.format(userCalendar['id'], j, jcount)) @@ -4311,7 +4053,7 @@ def infoCalendar(users): user, cal = buildCalendarGAPIObject(user) if not cal: continue - result = callGAPI(cal.calendarList(), 'get', + result = gapi.call(cal.calendarList(), 'get', soft_errors=True, calendarId=calendarId) if result: @@ -4330,7 +4072,7 @@ def printShowCalendars(users, csvFormat): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s calendars"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s calendars"' % (myarg, ['show', 'print'][csvFormat])) i = 0 count = len(users) for user in users: @@ -4384,7 +4126,7 @@ def printDriveSettings(users): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show drivesettings"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show drivesettings"' % sys.argv[i]) dont_show = ['kind', 'exportFormats', 'importFormats', 'maxUploadSize', 'maxImportSizes', 'user', 'appInstalled'] csvRows = [] titles = ['email',] @@ -4396,7 +4138,7 @@ def printDriveSettings(users): if not drive: continue sys.stderr.write('Getting Drive settings for %s (%s/%s)\n' % (user, i, count)) - feed = callGAPI(drive.about(), 'get', fields='*', soft_errors=True) + feed = gapi.call(drive.about(), 'get', fields='*', soft_errors=True) if feed is None: continue row = {'email': user} @@ -4420,7 +4162,7 @@ def getTeamDriveThemes(users): user, drive = buildDrive3GAPIObject(user) if not drive: continue - themes = callGAPI(drive.about(), 'get', fields='teamDriveThemes', soft_errors=True) + themes = gapi.call(drive.about(), 'get', fields='teamDriveThemes', soft_errors=True) if themes is None or 'teamDriveThemes' not in themes: continue print('theme') @@ -4447,7 +4189,7 @@ def printDriveActivity(users): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show driveactivity"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show driveactivity"' % sys.argv[i]) for user in users: user, activity = buildActivityGAPIObject(user) if not activity: @@ -4486,7 +4228,7 @@ def showDriveFileACL(users): useDomainAdminAccess = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam show drivefileacl".' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam show drivefileacl".' % sys.argv[i]) for user in users: user, drive = buildDrive3GAPIObject(user) if not drive: @@ -4513,7 +4255,7 @@ def getPermissionId(argstr): # We have to use v2 here since v3 has no permissions.getIdForEmail equivalent # https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4313 _, drive2 = buildDriveGAPIObject(_getValueFromOAuth('email')) - return callGAPI(drive2.permissions(), 'getIdForEmail', email=permissionId, fields='id')['id'] + return gapi.call(drive2.permissions(), 'getIdForEmail', email=permissionId, fields='id')['id'] def delDriveFileACL(users): fileId = sys.argv[5] @@ -4526,13 +4268,13 @@ def delDriveFileACL(users): useDomainAdminAccess = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam delete drivefileacl".' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam delete drivefileacl".' % sys.argv[i]) for user in users: user, drive = buildDrive3GAPIObject(user) if not drive: continue print('Removing permission for %s from %s' % (permissionId, fileId)) - callGAPI(drive.permissions(), 'delete', fileId=fileId, + gapi.call(drive.permissions(), 'delete', fileId=fileId, permissionId=permissionId, supportsAllDrives=True, useDomainAdminAccess=useDomainAdminAccess) @@ -4564,7 +4306,7 @@ def addDriveFileACL(users): body['domain'] = sys.argv[7] i = 8 else: - systemErrorExit(5, 'permission type must be user, group domain or anyone; got %s' % body['type']) + controlflow.system_error_exit(5, 'permission type must be user, group domain or anyone; got %s' % body['type']) while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'withlink': @@ -4576,7 +4318,7 @@ def addDriveFileACL(users): elif myarg == 'role': role = sys.argv[i+1].lower() if role not in DRIVEFILE_ACL_ROLES_MAP: - systemErrorExit(2, 'role must be {0}; got {1}'.format(', '.join(DRIVEFILE_ACL_ROLES_MAP), role)) + controlflow.system_error_exit(2, 'role must be {0}; got {1}'.format(', '.join(DRIVEFILE_ACL_ROLES_MAP), role)) body['role'] = DRIVEFILE_ACL_ROLES_MAP[role] if body['role'] == 'owner': sendNotificationEmail = True @@ -4596,12 +4338,12 @@ def addDriveFileACL(users): useDomainAdminAccess = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam add drivefileacl"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam add drivefileacl"' % sys.argv[i]) for user in users: user, drive = buildDrive3GAPIObject(user) if not drive: continue - result = callGAPI(drive.permissions(), 'create', fields='*', + result = gapi.call(drive.permissions(), 'create', fields='*', fileId=fileId, sendNotificationEmail=sendNotificationEmail, emailMessage=emailMessage, body=body, supportsAllDrives=True, transferOwnership=transferOwnership, @@ -4624,7 +4366,7 @@ def updateDriveFileACL(users): elif myarg == 'role': role = sys.argv[i+1].lower() if role not in DRIVEFILE_ACL_ROLES_MAP: - systemErrorExit(2, 'role must be {0}; got {1}'.format(', '.join(DRIVEFILE_ACL_ROLES_MAP), role)) + controlflow.system_error_exit(2, 'role must be {0}; got {1}'.format(', '.join(DRIVEFILE_ACL_ROLES_MAP), role)) body['role'] = DRIVEFILE_ACL_ROLES_MAP[role] if body['role'] == 'owner': transferOwnership = True @@ -4633,13 +4375,13 @@ def updateDriveFileACL(users): useDomainAdminAccess = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update drivefileacl"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update drivefileacl"' % sys.argv[i]) for user in users: user, drive = buildDrive3GAPIObject(user) if not drive: continue print('updating permissions for %s to file %s' % (permissionId, fileId)) - result = callGAPI(drive.permissions(), 'update', fields='*', + result = gapi.call(drive.permissions(), 'update', fields='*', fileId=fileId, permissionId=permissionId, removeExpiration=removeExpiration, transferOwnership=transferOwnership, body=body, supportsAllDrives=True, useDomainAdminAccess=useDomainAdminAccess) @@ -4685,7 +4427,7 @@ def printDriveFileList(users): else: orderByList.append('{0} desc'.format(fieldName)) else: - systemErrorExit(2, 'orderby must be one of {0}; got {1}'.format(', '.join(sorted(DRIVEFILE_ORDERBY_CHOICES_MAP)), fieldName)) + controlflow.system_error_exit(2, 'orderby must be one of {0}; got {1}'.format(', '.join(sorted(DRIVEFILE_ORDERBY_CHOICES_MAP)), fieldName)) elif myarg == 'query': query += ' and %s' % sys.argv[i+1] i += 2 @@ -4706,7 +4448,7 @@ def printDriveFileList(users): addFieldToCSVfile(myarg, {myarg: [DRIVEFILE_LABEL_CHOICES_MAP[myarg]]}, labelsList, fieldsTitles, titles) i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show filelist"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show filelist"' % myarg) if fieldsList or labelsList: fields = 'nextPageToken,items(' if fieldsList: @@ -4810,7 +4552,7 @@ def getFileIdFromAlternateLink(altLink): loc = fileId.find('&') if loc != -1: return fileId[:loc] - systemErrorExit(2, '%s is not a valid Drive File alternateLink' % altLink) + controlflow.system_error_exit(2, '%s is not a valid Drive File alternateLink' % altLink) def deleteDriveFile(users): fileIds = sys.argv[5] @@ -4825,7 +4567,7 @@ def deleteDriveFile(users): function = 'untrash' i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam delete drivefile"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam delete drivefile"' % sys.argv[i]) action = DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP[function] for user in users: user, drive = buildDriveGAPIObject(user) @@ -4893,9 +4635,9 @@ def showDriveFileTree(users): else: orderByList.append('{0} desc'.format(fieldName)) else: - systemErrorExit(2, 'orderby must be one of {0}; got {1}'.format(', '.join(sorted(DRIVEFILE_ORDERBY_CHOICES_MAP)), fieldName)) + controlflow.system_error_exit(2, 'orderby must be one of {0}; got {1}'.format(', '.join(sorted(DRIVEFILE_ORDERBY_CHOICES_MAP)), fieldName)) else: - systemErrorExit(2, '%s is not a valid argument for "gam show filetree"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show filetree"' % myarg) if orderByList: orderBy = ','.join(orderByList) else: @@ -4906,7 +4648,7 @@ def showDriveFileTree(users): user, drive = buildDriveGAPIObject(user) if not drive: continue - root_folder = callGAPI(drive.about(), 'get', fields='rootFolderId')['rootFolderId'] + root_folder = gapi.call(drive.about(), 'get', fields='rootFolderId')['rootFolderId'] sys.stderr.write('Getting all files for %s...\n' % user) page_message = ' Got %%%%total_items%%%% files for %s...\n' % user feed = callGAPIpages(drive.files(), 'list', 'items', page_message=page_message, @@ -4927,11 +4669,11 @@ def deleteEmptyDriveFolders(users): q=query, fields='items(title,id),nextPageToken') deleted_empty = False for folder in feed: - children = callGAPI(drive.children(), 'list', + children = gapi.call(drive.children(), 'list', folderId=folder['id'], fields='items(id)', maxResults=1) if 'items' not in children or not children['items']: print(utils.convertUTF8(' deleting empty folder %s...' % folder['title'])) - callGAPI(drive.files(), 'delete', fileId=folder['id']) + gapi.call(drive.files(), 'delete', fileId=folder['id']) deleted_empty = True else: print(utils.convertUTF8(' not deleting folder %s because it contains at least 1 item (%s)' % (folder['title'], children['items'][0]['id']))) @@ -4942,7 +4684,7 @@ def doEmptyDriveTrash(users): if not drive: continue print('Emptying Drive trash for %s' % user) - callGAPI(drive.files(), 'emptyTrash') + gapi.call(drive.files(), 'emptyTrash') def escapeDriveFileName(filename): if filename.find("'") == -1 and filename.find('\\') == -1: @@ -5008,7 +4750,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False): if mimeType in MIMETYPE_CHOICES_MAP: body['mimeType'] = MIMETYPE_CHOICES_MAP[mimeType] else: - systemErrorExit(2, 'mimetype must be one of %s; got %s"' % (', '.join(MIMETYPE_CHOICES_MAP), mimeType)) + controlflow.system_error_exit(2, 'mimetype must be one of %s; got %s"' % (', '.join(MIMETYPE_CHOICES_MAP), mimeType)) i += 2 elif myarg == 'parentid': body.setdefault('parents', []) @@ -5024,7 +4766,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False): body['writersCanShare'] = False i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s drivefile"' % (myarg, ['add', 'update'][update])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s drivefile"' % (myarg, ['add', 'update'][update])) return i def doUpdateDriveFile(users): @@ -5053,9 +4795,9 @@ def doUpdateDriveFile(users): else: i = getDriveFileAttribute(i, body, parameters, myarg, True) if not fileIdSelection['query'] and not fileIdSelection['fileIds']: - systemErrorExit(2, 'you need to specify either id, query or drivefilename in order to determine the file(s) to update') + controlflow.system_error_exit(2, 'you need to specify either id, query or drivefilename in order to determine the file(s) to update') if fileIdSelection['query'] and fileIdSelection['fileIds']: - systemErrorExit(2, 'you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.') + controlflow.system_error_exit(2, 'you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.') for user in users: user, drive = buildDriveGAPIObject(user) if not drive: @@ -5075,7 +4817,7 @@ def doUpdateDriveFile(users): media_body = googleapiclient.http.MediaFileUpload(parameters[DFA_LOCALFILEPATH], mimetype=parameters[DFA_LOCALMIMETYPE], resumable=True) for fileId in fileIdSelection['fileIds']: if media_body: - result = callGAPI(drive.files(), 'update', + result = gapi.call(drive.files(), 'update', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], @@ -5083,7 +4825,7 @@ def doUpdateDriveFile(users): supportsAllDrives=True) print('Successfully updated %s drive file with content from %s' % (result['id'], parameters[DFA_LOCALFILENAME])) else: - result = callGAPI(drive.files(), 'patch', + result = gapi.call(drive.files(), 'patch', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], body=body, @@ -5091,7 +4833,7 @@ def doUpdateDriveFile(users): print('Successfully updated drive file/folder ID %s' % (result['id'])) else: for fileId in fileIdSelection['fileIds']: - result = callGAPI(drive.files(), 'copy', + result = gapi.call(drive.files(), 'copy', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], @@ -5129,7 +4871,7 @@ def createDriveFile(users): body['parents'].append({'id': a_parent}) if parameters[DFA_LOCALFILEPATH]: media_body = googleapiclient.http.MediaFileUpload(parameters[DFA_LOCALFILEPATH], mimetype=parameters[DFA_LOCALMIMETYPE], resumable=True) - result = callGAPI(drive.files(), 'insert', + result = gapi.call(drive.files(), 'insert', convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], media_body=media_body, body=body, fields='id,title,mimeType', @@ -5182,7 +4924,7 @@ def downloadDriveFile(users): if exportFormat in DOCUMENT_FORMATS_MAP: exportFormats.extend(DOCUMENT_FORMATS_MAP[exportFormat]) else: - systemErrorExit(2, 'format must be one of {0}; got {1}'.format(', '.join(DOCUMENT_FORMATS_MAP), exportFormat)) + controlflow.system_error_exit(2, 'format must be one of {0}; got {1}'.format(', '.join(DOCUMENT_FORMATS_MAP), exportFormat)) i += 2 elif myarg == 'targetfolder': targetFolder = os.path.expanduser(sys.argv[i+1]) @@ -5200,11 +4942,11 @@ def downloadDriveFile(users): showProgress = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam get drivefile"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam get drivefile"' % sys.argv[i]) if not fileIdSelection['query'] and not fileIdSelection['fileIds']: - systemErrorExit(2, 'you need to specify either id, query or drivefilename in order to determine the file(s) to download') + controlflow.system_error_exit(2, 'you need to specify either id, query or drivefilename in order to determine the file(s) to download') if fileIdSelection['query'] and fileIdSelection['fileIds']: - systemErrorExit(2, 'you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.') + controlflow.system_error_exit(2, 'you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.') if csvSheetTitle: exportFormatName = 'csv' exportFormatChoices = [exportFormatName] @@ -5228,7 +4970,7 @@ def downloadDriveFile(users): i = 0 for fileId in fileIdSelection['fileIds']: fileExtension = None - result = callGAPI(drive.files(), 'get', + result = gapi.call(drive.files(), 'get', fileId=fileId, fields='fileExtension,fileSize,mimeType,title', supportsAllDrives=True) fileExtension = result.get('fileExtension') mimeType = result['mimeType'] @@ -5282,7 +5024,7 @@ def downloadDriveFile(users): if revisionId: request.uri = '{0}&revision={1}'.format(request.uri, revisionId) else: - spreadsheet = callGAPI(sheet.spreadsheets(), 'get', + spreadsheet = gapi.call(sheet.spreadsheets(), 'get', spreadsheetId=fileId, fields='spreadsheetUrl,sheets(properties(sheetId,title))') for sheet in spreadsheet['sheets']: if sheet['properties']['title'].lower() == csvSheetTitleLower: @@ -5290,7 +5032,7 @@ def downloadDriveFile(users): fileId, sheet['properties']['sheetId']) break else: - stderrErrorMsg('Google Doc: %s, Sheet: %s, does not exist' % (result['title'], csvSheetTitle)) + display.print_error('Google Doc: %s, Sheet: %s, does not exist' % (result['title'], csvSheetTitle)) csvSheetNotFound = True continue else: @@ -5315,23 +5057,23 @@ def downloadDriveFile(users): fileDownloaded = True break except (IOError, httplib2.HttpLib2Error) as e: - stderrErrorMsg(str(e)) + display.print_error(str(e)) GM_Globals[GM_SYSEXITRC] = 6 fileDownloadFailed = True break except googleapiclient.http.HttpError as e: mg = HTTP_ERROR_PATTERN.match(str(e)) if mg: - stderrErrorMsg(mg.group(1)) + display.print_error(mg.group(1)) else: - stderrErrorMsg(str(e)) + display.print_error(str(e)) fileDownloadFailed = True break if fh and not targetStdout: closeFile(fh) os.remove(filename) if not fileDownloaded and not fileDownloadFailed and not csvSheetNotFound: - stderrErrorMsg('Format ({0}) not available'.format(','.join(exportFormatChoices))) + display.print_error('Format ({0}) not available'.format(','.join(exportFormatChoices))) GM_Globals[GM_SYSEXITRC] = 51 def showDriveFileInfo(users): @@ -5351,7 +5093,7 @@ def showDriveFileInfo(users): labelsList.append(DRIVEFILE_LABEL_CHOICES_MAP[myarg]) i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show fileinfo"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show fileinfo"' % myarg) if fieldsList or labelsList: fieldsList.append('title') fields = ','.join(set(fieldsList)) @@ -5363,7 +5105,7 @@ def showDriveFileInfo(users): user, drive = buildDriveGAPIObject(user) if not drive: continue - feed = callGAPI(drive.files(), 'get', fileId=fileId, fields=fields, supportsAllDrives=True) + feed = gapi.call(drive.files(), 'get', fileId=fileId, fields=fields, supportsAllDrives=True) if feed: print_json(None, feed) @@ -5373,7 +5115,7 @@ def showDriveFileRevisions(users): user, drive = buildDriveGAPIObject(user) if not drive: continue - feed = callGAPI(drive.revisions(), 'list', fileId=fileId) + feed = gapi.call(drive.revisions(), 'list', fileId=fileId) if feed: print_json(None, feed) @@ -5390,7 +5132,7 @@ def transferSecCals(users): sendNotifications = getBoolean(sys.argv[i+1], myarg) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam transfer seccals"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam transfer seccals"' % sys.argv[i]) if remove_source_user: target_user, target_cal = buildCalendarGAPIObject(target_user) if not target_cal: @@ -5404,10 +5146,10 @@ def transferSecCals(users): for calendar in calendars: calendarId = calendar['id'] if calendarId.find('@group.calendar.google.com') != -1: - callGAPI(source_cal.acl(), 'insert', calendarId=calendarId, + gapi.call(source_cal.acl(), 'insert', calendarId=calendarId, body={'role': 'owner', 'scope': {'type': 'user', 'value': target_user}}, sendNotifications=sendNotifications) if remove_source_user: - callGAPI(target_cal.acl(), 'insert', calendarId=calendarId, + gapi.call(target_cal.acl(), 'insert', calendarId=calendarId, body={'role': 'none', 'scope': {'type': 'user', 'value': user}}, sendNotifications=sendNotifications) def transferDriveFiles(users): @@ -5420,11 +5162,11 @@ def transferDriveFiles(users): remove_source_user = False i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam transfer drive"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam transfer drive"' % sys.argv[i]) target_user, target_drive = buildDriveGAPIObject(target_user) if not target_drive: return - target_about = callGAPI(target_drive.about(), 'get', fields='quotaType,quotaBytesTotal,quotaBytesUsed') + target_about = gapi.call(target_drive.about(), 'get', fields='quotaType,quotaBytesTotal,quotaBytesUsed') if target_about['quotaType'] != 'UNLIMITED': target_drive_free = int(target_about['quotaBytesTotal']) - int(target_about['quotaBytesUsed']) else: @@ -5434,11 +5176,11 @@ def transferDriveFiles(users): if not source_drive: continue counter = 0 - source_about = callGAPI(source_drive.about(), 'get', fields='quotaBytesTotal,quotaBytesUsed,rootFolderId,permissionId') + source_about = gapi.call(source_drive.about(), 'get', fields='quotaBytesTotal,quotaBytesUsed,rootFolderId,permissionId') source_drive_size = int(source_about['quotaBytesUsed']) if target_drive_free is not None: if target_drive_free < source_drive_size: - systemErrorExit(4, MESSAGE_NO_TRANSFER_LACK_OF_DISK_SPACE.format(source_drive_size / 1024 / 1024, target_drive_free / 1024 / 1024)) + controlflow.system_error_exit(4, MESSAGE_NO_TRANSFER_LACK_OF_DISK_SPACE.format(source_drive_size / 1024 / 1024, target_drive_free / 1024 / 1024)) print('Source drive size: %smb Target drive free: %smb' % (source_drive_size / 1024 / 1024, target_drive_free / 1024 / 1024)) target_drive_free = target_drive_free - source_drive_size # prep target_drive_free for next user else: @@ -5465,7 +5207,7 @@ def transferDriveFiles(users): target_top_folder = target_folder['id'] got_top_folder = True if not got_top_folder: - create_folder = callGAPI(target_drive.files(), 'insert', body={'title': '%s old files' % user, 'mimeType': 'application/vnd.google-apps.folder'}, fields='id') + create_folder = gapi.call(target_drive.files(), 'insert', body={'title': '%s old files' % user, 'mimeType': 'application/vnd.google-apps.folder'}, fields='id') target_top_folder = create_folder['id'] transferred_files = [] while True: # we loop thru, skipping files until all of their parents are done @@ -5490,7 +5232,7 @@ def transferDriveFiles(users): counter += 1 print('Changing owner for file %s (%s/%s)' % (drive_file['id'], counter, total_count)) body = {'role': 'owner', 'type': 'user', 'value': target_user} - callGAPI(source_drive.permissions(), 'insert', soft_errors=True, fileId=file_id, sendNotificationEmails=False, body=body) + gapi.call(source_drive.permissions(), 'insert', soft_errors=True, fileId=file_id, sendNotificationEmails=False, body=body) target_parents = [] for parent in source_parents: try: @@ -5502,9 +5244,9 @@ def transferDriveFiles(users): pass if not target_parents: target_parents.append({'id': target_top_folder}) - callGAPI(target_drive.files(), 'patch', soft_errors=True, retry_reasons=['notFound'], fileId=file_id, body={'parents': target_parents}) + gapi.call(target_drive.files(), 'patch', soft_errors=True, retry_reasons=['notFound'], fileId=file_id, body={'parents': target_parents}) if remove_source_user: - callGAPI(target_drive.permissions(), 'delete', soft_errors=True, fileId=file_id, permissionId=source_permissionid) + gapi.call(target_drive.permissions(), 'delete', soft_errors=True, fileId=file_id, permissionId=source_permissionid) if not skipped_files: break @@ -5556,7 +5298,7 @@ def sendOrDropEmail(users, method='send'): elif method == 'import' and myarg == 'processforcalendar': kwargs['processForCalendar'] = True else: - systemErrorExit(2, '%s is not a valid argument for "gam %semail"' % (sys.argv[i], method)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %semail"' % (sys.argv[i], method)) for user in users: send_email(subject, body, recipient, sender, user, method, labels, msgHeaders, kwargs) @@ -5575,16 +5317,16 @@ def doImap(users): body['expungeBehavior'] = EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP[opt] i += 2 else: - systemErrorExit(2, 'value for "gam imap expungebehavior" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP), opt)) + controlflow.system_error_exit(2, 'value for "gam imap expungebehavior" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP), opt)) elif myarg == 'maxfoldersize': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES: body['maxFolderSize'] = int(opt) i += 2 else: - systemErrorExit(2, 'value for "gam imap maxfoldersize" must be one of %s; got %s' % ('|'.join(EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES), opt)) + controlflow.system_error_exit(2, 'value for "gam imap maxfoldersize" must be one of %s; got %s' % ('|'.join(EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES), opt)) else: - systemErrorExit(2, '%s is not a valid argument for "gam imap"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam imap"' % myarg) i = 0 count = len(users) for user in users: @@ -5593,7 +5335,7 @@ def doImap(users): if not gmail: continue print("Setting IMAP Access to %s for %s (%s/%s)" % (str(enable), user, i, count)) - callGAPI(gmail.users().settings(), 'updateImap', + gapi.call(gmail.users().settings(), 'updateImap', soft_errors=True, userId='me', body=body) @@ -5607,7 +5349,7 @@ def doLanguage(users): if not gmail: continue print('Setting language to %s for %s (%s/%s)' % (displayLanguage, user, i, count)) - result = callGAPI(gmail.users().settings(), 'updateLanguage', userId='me', body={'displayLanguage': displayLanguage}) + result = gapi.call(gmail.users().settings(), 'updateLanguage', userId='me', body={'displayLanguage': displayLanguage}) print('Language is set to %s for %s' % (result['displayLanguage'], user)) def getLanguage(users): @@ -5618,7 +5360,7 @@ def getLanguage(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings(), 'getLanguage', + result = gapi.call(gmail.users().settings(), 'getLanguage', soft_errors=True, userId='me') if result: @@ -5632,7 +5374,7 @@ def getImap(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings(), 'getImap', + result = gapi.call(gmail.users().settings(), 'getImap', soft_errors=True, userId='me') if result: @@ -5664,20 +5406,20 @@ def doLicense(users, operation): for user in users: if operation == 'delete': print('Removing license %s from user %s' % (_formatSKUIdDisplayName(skuId), user)) - callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, userId=user) + gapi.call(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, userId=user) elif operation == 'insert': print('Adding license %s to user %s' % (_formatSKUIdDisplayName(skuId), user)) - callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, body={'userId': user}) + gapi.call(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, body={'userId': user}) elif operation == 'patch': try: old_sku = sys.argv[i] if old_sku.lower() == 'from': old_sku = sys.argv[i+1] except KeyError: - systemErrorExit(2, 'You need to specify the user\'s old SKU as the last argument') + controlflow.system_error_exit(2, 'You need to specify the user\'s old SKU as the last argument') _, old_sku = getProductAndSKU(old_sku) print('Changing user %s from license %s to %s' % (user, _formatSKUIdDisplayName(old_sku), _formatSKUIdDisplayName(skuId))) - callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=old_sku, userId=user, body={'skuId': skuId}) + gapi.call(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=old_sku, userId=user, body={'skuId': skuId}) def doPop(users): enable = getBoolean(sys.argv[4], 'gam pop') @@ -5691,18 +5433,18 @@ def doPop(users): body['accessWindow'] = EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP[opt] i += 2 else: - systemErrorExit(2, 'value for "gam pop for" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP), opt)) + controlflow.system_error_exit(2, 'value for "gam pop for" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP), opt)) elif myarg == 'action': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP: body['disposition'] = EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP[opt] i += 2 else: - systemErrorExit(2, 'value for "gam pop action" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP), opt)) + controlflow.system_error_exit(2, 'value for "gam pop action" must be one of %s; got %s' % (', '.join(EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP), opt)) elif myarg == 'confirm': i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam pop"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam pop"' % myarg) i = 0 count = len(users) for user in users: @@ -5711,7 +5453,7 @@ def doPop(users): if not gmail: continue print("Setting POP Access to %s for %s (%s/%s)" % (str(enable), user, i, count)) - callGAPI(gmail.users().settings(), 'updatePop', + gapi.call(gmail.users().settings(), 'updatePop', soft_errors=True, userId='me', body=body) @@ -5723,7 +5465,7 @@ def getPop(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings(), 'getPop', + result = gapi.call(gmail.users().settings(), 'getPop', soft_errors=True, userId='me') if result: @@ -5806,7 +5548,7 @@ def getSendAsAttributes(i, myarg, body, tagReplacements, command): body['treatAsAlias'] = getBoolean(sys.argv[i+1], myarg) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s"' % (sys.argv[i], command)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s"' % (sys.argv[i], command)) return i SMTPMSA_PORTS = ['25', '465', '587'] @@ -5846,7 +5588,7 @@ def addUpdateSendAs(users, i, addCmd): elif myarg == 'smtpmsa.port': value = sys.argv[i+1].lower() if value not in SMTPMSA_PORTS: - systemErrorExit(2, '{0} must be {1}; got {2}'.format(myarg, ', '.join(SMTPMSA_PORTS), value)) + controlflow.system_error_exit(2, '{0} must be {1}; got {2}'.format(myarg, ', '.join(SMTPMSA_PORTS), value)) smtpMsa['port'] = int(value) i += 2 elif myarg == 'smtpmsa.username': @@ -5858,11 +5600,11 @@ def addUpdateSendAs(users, i, addCmd): elif myarg == 'smtpmsa.securitymode': value = sys.argv[i+1].lower() if value not in SMTPMSA_SECURITY_MODES: - systemErrorExit(2, '{0} must be {1}; got {2}'.format(myarg, ', '.join(SMTPMSA_SECURITY_MODES), value)) + controlflow.system_error_exit(2, '{0} must be {1}; got {2}'.format(myarg, ', '.join(SMTPMSA_SECURITY_MODES), value)) smtpMsa['securityMode'] = value i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s"' % (sys.argv[i], command)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s"' % (sys.argv[i], command)) else: i = getSendAsAttributes(i, myarg, body, tagReplacements, command) if signature is not None: @@ -5870,7 +5612,7 @@ def addUpdateSendAs(users, i, addCmd): if smtpMsa: for field in SMTPMSA_REQUIRED_FIELDS: if field not in smtpMsa: - systemErrorExit(2, 'smtpmsa.{0} is required.'.format(field)) + controlflow.system_error_exit(2, 'smtpmsa.{0} is required.'.format(field)) body['smtpMsa'] = smtpMsa kwargs = {'body': body} if not addCmd: @@ -5883,7 +5625,7 @@ def addUpdateSendAs(users, i, addCmd): if not gmail: continue print("Allowing %s to send as %s (%s/%s)" % (user, emailAddress, i, count)) - callGAPI(gmail.users().settings().sendAs(), ['patch', 'create'][addCmd], + gapi.call(gmail.users().settings().sendAs(), ['patch', 'create'][addCmd], soft_errors=True, userId='me', **kwargs) @@ -5897,7 +5639,7 @@ def deleteSendAs(users): if not gmail: continue print("Disallowing %s to send as %s (%s/%s)" % (user, emailAddress, i, count)) - callGAPI(gmail.users().settings().sendAs(), 'delete', + gapi.call(gmail.users().settings().sendAs(), 'delete', soft_errors=True, userId='me', sendAsEmail=emailAddress) @@ -5918,7 +5660,7 @@ def updateSmime(users): make_default = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam update smime"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam update smime"' % myarg) if not make_default: print('Nothing to update for smime.') sys.exit(0) @@ -5928,17 +5670,17 @@ def updateSmime(users): continue sendAsEmail = sendAsEmailBase if sendAsEmailBase else user if not smimeIdBase: - result = callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'list', userId='me', sendAsEmail=sendAsEmail, fields='smimeInfo(id)') + result = gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'list', userId='me', sendAsEmail=sendAsEmail, fields='smimeInfo(id)') smimes = result.get('smimeInfo', []) if not smimes: - systemErrorExit(3, '%s has no S/MIME certificates for sendas address %s' % (user, sendAsEmail)) + controlflow.system_error_exit(3, '%s has no S/MIME certificates for sendas address %s' % (user, sendAsEmail)) if len(smimes) > 1: - systemErrorExit(3, '%s has more than one S/MIME certificate. Please specify a cert to update:\n %s' % (user, '\n '.join([smime['id'] for smime in smimes]))) + controlflow.system_error_exit(3, '%s has more than one S/MIME certificate. Please specify a cert to update:\n %s' % (user, '\n '.join([smime['id'] for smime in smimes]))) smimeId = smimes[0]['id'] else: smimeId = smimeIdBase print('Setting smime id %s as default for user %s and sendas %s' % (smimeId, user, sendAsEmail)) - callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'setDefault', userId='me', sendAsEmail=sendAsEmail, id=smimeId) + gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'setDefault', userId='me', sendAsEmail=sendAsEmail, id=smimeId) def deleteSmime(users): smimeIdBase = None @@ -5953,24 +5695,24 @@ def deleteSmime(users): sendAsEmailBase = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam delete smime"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam delete smime"' % myarg) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue sendAsEmail = sendAsEmailBase if sendAsEmailBase else user if not smimeIdBase: - result = callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'list', userId='me', sendAsEmail=sendAsEmail, fields='smimeInfo(id)') + result = gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'list', userId='me', sendAsEmail=sendAsEmail, fields='smimeInfo(id)') smimes = result.get('smimeInfo', []) if not smimes: - systemErrorExit(3, '%s has no S/MIME certificates for sendas address %s' % (user, sendAsEmail)) + controlflow.system_error_exit(3, '%s has no S/MIME certificates for sendas address %s' % (user, sendAsEmail)) if len(smimes) > 1: - systemErrorExit(3, '%s has more than one S/MIME certificate. Please specify a cert to delete:\n %s' % (user, '\n '.join([smime['id'] for smime in smimes]))) + controlflow.system_error_exit(3, '%s has more than one S/MIME certificate. Please specify a cert to delete:\n %s' % (user, '\n '.join([smime['id'] for smime in smimes]))) smimeId = smimes[0]['id'] else: smimeId = smimeIdBase print('Deleting smime id %s for user %s and sendas %s' % (smimeId, user, sendAsEmail)) - callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'delete', userId='me', sendAsEmail=sendAsEmail, id=smimeId) + gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'delete', userId='me', sendAsEmail=sendAsEmail, id=smimeId) def printShowSmime(users, csvFormat): if csvFormat: @@ -5988,7 +5730,7 @@ def printShowSmime(users, csvFormat): primaryonly = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument for "gam %s smime"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam %s smime"' % (myarg, ['show', 'print'][csvFormat])) i = 0 for user in users: i += 1 @@ -5998,12 +5740,12 @@ def printShowSmime(users, csvFormat): if primaryonly: sendAsEmails = [user] else: - result = callGAPI(gmail.users().settings().sendAs(), 'list', userId='me', fields='sendAs(sendAsEmail)') + result = gapi.call(gmail.users().settings().sendAs(), 'list', userId='me', fields='sendAs(sendAsEmail)') sendAsEmails = [] for sendAs in result['sendAs']: sendAsEmails.append(sendAs['sendAsEmail']) for sendAsEmail in sendAsEmails: - result = callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'list', sendAsEmail=sendAsEmail, userId='me') + result = gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'list', sendAsEmail=sendAsEmail, userId='me') smimes = result.get('smimeInfo', []) for j, _ in enumerate(smimes): smimes[j]['expiration'] = utils.formatTimestampYMDHMS(smimes[j]['expiration']) @@ -6031,7 +5773,7 @@ def printShowSendAs(users, csvFormat): formatSig = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s sendas"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s sendas"' % (myarg, ['show', 'print'][csvFormat])) i = 0 count = len(users) for user in users: @@ -6039,7 +5781,7 @@ def printShowSendAs(users, csvFormat): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings().sendAs(), 'list', + result = gapi.call(gmail.users().settings().sendAs(), 'list', soft_errors=True, userId='me') jcount = len(result.get('sendAs', [])) if (result) else 0 @@ -6082,7 +5824,7 @@ def infoSendAs(users): formatSig = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam info sendas"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info sendas"' % sys.argv[i]) i = 0 count = len(users) for user in users: @@ -6091,7 +5833,7 @@ def infoSendAs(users): if not gmail: continue print('User: {0}, Show SendAs Address:{1}'.format(user, currentCount(i, count))) - result = callGAPI(gmail.users().settings().sendAs(), 'get', + result = gapi.call(gmail.users().settings().sendAs(), 'get', soft_errors=True, userId='me', sendAsEmail=emailAddress) if result: @@ -6119,9 +5861,9 @@ def addSmime(users): sendAsEmailBase = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam add smime"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam add smime"' % myarg) if 'pkcs12' not in body: - systemErrorExit(3, 'you must specify a file to upload') + controlflow.system_error_exit(3, 'you must specify a file to upload') i = 0 for user in users: i += 1 @@ -6129,9 +5871,9 @@ def addSmime(users): if not gmail: continue sendAsEmail = sendAsEmailBase if sendAsEmailBase else user - result = callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'insert', userId='me', sendAsEmail=sendAsEmail, body=body) + result = gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'insert', userId='me', sendAsEmail=sendAsEmail, body=body) if setDefault: - callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'setDefault', userId='me', sendAsEmail=sendAsEmail, id=result['id']) + gapi.call(gmail.users().settings().sendAs().smimeInfo(), 'setDefault', userId='me', sendAsEmail=sendAsEmail, id=result['id']) print('Added S/MIME certificate for user %s sendas %s issued by %s' % (user, sendAsEmail, result['issuerCn'])) def getLabelAttributes(i, myarg, body): @@ -6144,12 +5886,12 @@ def getLabelAttributes(i, myarg, body): elif value == 'showifunread': body['labelListVisibility'] = 'labelShowIfUnread' else: - systemErrorExit(2, 'label_list_visibility must be one of hide, show, show_if_unread; got %s' % value) + controlflow.system_error_exit(2, 'label_list_visibility must be one of hide, show, show_if_unread; got %s' % value) i += 2 elif myarg == 'messagelistvisibility': value = sys.argv[i+1].lower().replace('_', '') if value not in ['hide', 'show']: - systemErrorExit(2, 'message_list_visibility must be show or hide; got %s' % value) + controlflow.system_error_exit(2, 'message_list_visibility must be show or hide; got %s' % value) body['messageListVisibility'] = value i += 2 elif myarg == 'backgroundcolor': @@ -6161,7 +5903,7 @@ def getLabelAttributes(i, myarg, body): body['color']['textColor'] = getLabelColor(sys.argv[i+1]) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for this command.' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for this command.' % myarg) return i def checkLabelColor(body): @@ -6170,8 +5912,8 @@ def checkLabelColor(body): if 'backgroundColor' in body['color']: if 'textColor' in body['color']: return - systemErrorExit(2, 'textcolor is required.') - systemErrorExit(2, 'backgroundcolor is required.') + controlflow.system_error_exit(2, 'textcolor is required.') + controlflow.system_error_exit(2, 'backgroundcolor is required.') def doLabel(users, i): label = sys.argv[i] @@ -6189,7 +5931,7 @@ def doLabel(users, i): if not gmail: continue print("Creating label %s for %s (%s/%s)" % (label, user, i, count)) - callGAPI(gmail.users().labels(), 'create', soft_errors=True, userId=user, body=body) + gapi.call(gmail.users().labels(), 'create', soft_errors=True, userId=user, body=body) PROCESS_MESSAGE_FUNCTION_TO_ACTION_MAP = {'delete': 'deleted', 'trash': 'trashed', 'untrash': 'untrashed', 'modify': 'modified'} @@ -6208,7 +5950,7 @@ def labelsToLabelIds(gmail, labels): for label in labels: if label not in allLabels: # first refresh labels in user mailbox - label_results = callGAPI(gmail.users().labels(), 'list', + label_results = gapi.call(gmail.users().labels(), 'list', userId='me', fields='labels(id,name,type)') for a_label in label_results['labels']: if a_label['type'] == 'system': @@ -6217,7 +5959,7 @@ def labelsToLabelIds(gmail, labels): allLabels[a_label['name']] = a_label['id'] if label not in allLabels: # if still not there, create it - label_results = callGAPI(gmail.users().labels(), 'create', + label_results = gapi.call(gmail.users().labels(), 'create', body={'labelListVisibility': 'labelShow', 'messageListVisibility': 'show', 'name': label}, userId='me', fields='id') @@ -6231,7 +5973,7 @@ def labelsToLabelIds(gmail, labels): parent_label = label[:label.rfind('/')] while True: if not parent_label in allLabels: - label_result = callGAPI(gmail.users().labels(), 'create', + label_result = gapi.call(gmail.users().labels(), 'create', userId='me', body={'name': parent_label}) allLabels[parent_label] = label_result['id'] if parent_label.find('/') == -1: @@ -6265,9 +6007,9 @@ def doProcessMessagesOrThreads(users, function, unit='messages'): body['removeLabelIds'].append(sys.argv[i+1]) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s %s"' % (sys.argv[i], function, unit)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s %s"' % (sys.argv[i], function, unit)) if not query: - systemErrorExit(2, 'No query specified. You must specify some query!') + controlflow.system_error_exit(2, 'No query specified. You must specify some query!') action = PROCESS_MESSAGE_FUNCTION_TO_ACTION_MAP[function] for user in users: user, gmail = buildGmailGAPIObject(user) @@ -6301,7 +6043,7 @@ def doProcessMessagesOrThreads(users, function, unit='messages'): for id_batch in id_batches: kwargs['body']['ids'] = id_batch print('%s %s messages' % (function, len(id_batch))) - callGAPI(unitmethod(), batchFunction, + gapi.call(unitmethod(), batchFunction, userId='me', **kwargs) processed_messages += len(id_batch) print('%s %s of %s messages' % (function, processed_messages, result_count)) @@ -6311,7 +6053,7 @@ def doProcessMessagesOrThreads(users, function, unit='messages'): for a_unit in listResult: i += 1 print(' %s %s %s for user %s (%s/%s)' % (function, unit, a_unit['id'], user, i, result_count)) - callGAPI(unitmethod(), function, + gapi.call(unitmethod(), function, id=a_unit['id'], userId='me', **kwargs) def doDeleteLabel(users): @@ -6322,7 +6064,7 @@ def doDeleteLabel(users): if not gmail: continue print('Getting all labels for %s...' % user) - labels = callGAPI(gmail.users().labels(), 'list', userId=user, fields='labels(id,name,type)') + labels = gapi.call(gmail.users().labels(), 'list', userId=user, fields='labels(id,name,type)') del_labels = [] if label == '--ALL_LABELS--': for del_label in labels['labels']: @@ -6377,12 +6119,12 @@ def showLabels(users): showCounts = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show labels"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show labels"' % sys.argv[i]) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue - labels = callGAPI(gmail.users().labels(), 'list', userId=user, soft_errors=True) + labels = gapi.call(gmail.users().labels(), 'list', userId=user, soft_errors=True) if labels: for label in labels['labels']: if onlyUser and (label['type'] == 'system'): @@ -6393,7 +6135,7 @@ def showLabels(users): continue print(' %s: %s' % (a_key, label[a_key])) if showCounts: - counts = callGAPI(gmail.users().labels(), 'get', + counts = gapi.call(gmail.users().labels(), 'get', userId=user, id=label['id'], fields='messagesTotal,messagesUnread,threadsTotal,threadsUnread') for a_key in counts: @@ -6409,7 +6151,7 @@ def showGmailProfile(users): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for gam show gmailprofile' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for gam show gmailprofile' % sys.argv[i]) csvRows = [] titles = ['emailAddress'] i = 0 @@ -6421,15 +6163,15 @@ def showGmailProfile(users): continue sys.stderr.write('Getting Gmail profile for %s\n' % user) try: - results = callGAPI(gmail.users(), 'getProfile', - throw_reasons=GAPI_GMAIL_THROW_REASONS, + results = gapi.call(gmail.users(), 'getProfile', + throw_reasons=gapi.errors.ErrorReason.GMAIL_THROW_REASONS, userId='me') if results: for item in results: if item not in titles: titles.append(item) csvRows.append(results) - except GAPI_serviceNotAvailable: + except gapi.errors.gapi.errors.GapiServiceNotAvailableError: entityServiceNotApplicableWarning('User', user, i, count) sortCSVTitles(['emailAddress',], titles) writeCSVfile(csvRows, titles, list_type='Gmail Profiles', todrive=todrive) @@ -6451,10 +6193,10 @@ def updateLabels(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - labels = callGAPI(gmail.users().labels(), 'list', userId=user, fields='labels(id,name)') + labels = gapi.call(gmail.users().labels(), 'list', userId=user, fields='labels(id,name)') for label in labels['labels']: if label['name'].lower() == label_name_lower: - callGAPI(gmail.users().labels(), 'patch', soft_errors=True, + gapi.call(gmail.users().labels(), 'patch', soft_errors=True, userId=user, id=label['id'], body=body) break else: @@ -6482,13 +6224,13 @@ def renameLabels(users): merge = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam rename label"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam rename label"' % sys.argv[i]) pattern = re.compile(search, re.IGNORECASE) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue - labels = callGAPI(gmail.users().labels(), 'list', userId=user) + labels = gapi.call(gmail.users().labels(), 'list', userId=user) for label in labels['labels']: if label['type'] == 'system': continue @@ -6497,11 +6239,11 @@ def renameLabels(users): try: new_label_name = replace % match_result.groups() except TypeError: - systemErrorExit(2, 'The number of subfields ({0}) in search "{1}" does not match the number of subfields ({2}) in replace "{3}"'.format(len(match_result.groups()), search, replace.count('%s'), replace)) + controlflow.system_error_exit(2, 'The number of subfields ({0}) in search "{1}" does not match the number of subfields ({2}) in replace "{3}"'.format(len(match_result.groups()), search, replace.count('%s'), replace)) print(' Renaming "%s" to "%s"' % (label['name'], new_label_name)) try: - callGAPI(gmail.users().labels(), 'patch', soft_errors=True, throw_reasons=[GAPI_ABORTED], id=label['id'], userId=user, body={'name': new_label_name}) - except GAPI_aborted: + gapi.call(gmail.users().labels(), 'patch', soft_errors=True, throw_reasons=[gapi.errors.ErrorReason.ABORTED], id=label['id'], userId=user, body={'name': new_label_name}) + except gapi.errors.gapi.errors.GapiAbortedError: if merge: print(' Merging %s label to existing %s label' % (label['name'], new_label_name)) messages_to_relabel = callGAPIpages(gmail.users().messages(), 'list', 'messages', @@ -6515,24 +6257,24 @@ def renameLabels(users): j = 1 for message_to_relabel in messages_to_relabel: print(' relabeling message %s (%s/%s)' % (message_to_relabel['id'], j, len(messages_to_relabel))) - callGAPI(gmail.users().messages(), 'modify', userId=user, id=message_to_relabel['id'], body=body) + gapi.call(gmail.users().messages(), 'modify', userId=user, id=message_to_relabel['id'], body=body) j += 1 else: print(' no messages with %s label' % label['name']) print(' Deleting label %s' % label['name']) - callGAPI(gmail.users().labels(), 'delete', id=label['id'], userId=user) + gapi.call(gmail.users().labels(), 'delete', id=label['id'], userId=user) else: print(' Error: looks like %s already exists, not renaming. Use the "merge" argument to merge the labels' % new_label_name) def _getUserGmailLabels(gmail, user, i, count, **kwargs): try: - labels = callGAPI(gmail.users().labels(), 'list', - throw_reasons=GAPI_GMAIL_THROW_REASONS, + labels = gapi.call(gmail.users().labels(), 'list', + throw_reasons=gapi.errors.ErrorReason.GMAIL_THROW_REASONS, userId='me', **kwargs) if not labels: labels = {'labels': []} return labels - except GAPI_serviceNotAvailable: + except gapi.errors.GapiServiceNotAvailableError: entityServiceNotApplicableWarning('User', user, i, count) return None @@ -6632,7 +6374,7 @@ def addFilter(users, i): elif myarg == 'size': body['criteria']['sizeComparison'] = sys.argv[i+1].lower() if body['criteria']['sizeComparison'] not in ['larger', 'smaller']: - systemErrorExit(2, 'size must be followed by larger or smaller; got %s' % sys.argv[i+1].lower()) + controlflow.system_error_exit(2, 'size must be followed by larger or smaller; got %s' % sys.argv[i+1].lower()) body['criteria'][myarg] = sys.argv[i+2] i += 3 elif myarg in FILTER_ACTION_CHOICES: @@ -6669,11 +6411,11 @@ def addFilter(users, i): body['action']['forward'] = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam filter"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam filter"' % sys.argv[i]) if 'criteria' not in body: - systemErrorExit(2, 'you must specify a crtieria <{0}> for "gam filter"'.format('|'.join(FILTER_CRITERIA_CHOICES_MAP))) + controlflow.system_error_exit(2, 'you must specify a crtieria <{0}> for "gam filter"'.format('|'.join(FILTER_CRITERIA_CHOICES_MAP))) if 'action' not in body: - systemErrorExit(2, 'you must specify an action <{0}> for "gam filter"'.format('|'.join(FILTER_ACTION_CHOICES))) + controlflow.system_error_exit(2, 'you must specify an action <{0}> for "gam filter"'.format('|'.join(FILTER_ACTION_CHOICES))) if removeLabelIds: body['action']['removeLabelIds'] = removeLabelIds i = 0 @@ -6693,7 +6435,7 @@ def addFilter(users, i): body['action']['addLabelIds'] = [] addLabelId = _getLabelId(labels, addLabelName) if not addLabelId: - result = callGAPI(gmail.users().labels(), 'create', + result = gapi.call(gmail.users().labels(), 'create', soft_errors=True, userId='me', body={'name': addLabelName}, fields='id') if not result: @@ -6701,7 +6443,7 @@ def addFilter(users, i): addLabelId = result['id'] body['action']['addLabelIds'].append(addLabelId) print("Adding filter for %s (%s/%s)" % (user, i, count)) - result = callGAPI(gmail.users().settings().filters(), 'create', + result = gapi.call(gmail.users().settings().filters(), 'create', soft_errors=True, userId='me', body=body) if result: @@ -6717,7 +6459,7 @@ def deleteFilters(users): if not gmail: continue print("Deleting filter %s for %s (%s/%s)" % (filterId, user, i, count)) - callGAPI(gmail.users().settings().filters(), 'delete', + gapi.call(gmail.users().settings().filters(), 'delete', soft_errors=True, userId='me', id=filterId) @@ -6733,7 +6475,7 @@ def printShowFilters(users, csvFormat): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s filter"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s filter"' % (myarg, ['show', 'print'][csvFormat])) i = 0 count = len(users) for user in users: @@ -6741,12 +6483,12 @@ def printShowFilters(users, csvFormat): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - labels = callGAPI(gmail.users().labels(), 'list', + labels = gapi.call(gmail.users().labels(), 'list', soft_errors=True, userId='me', fields='labels(id,name)') if not labels: labels = {'labels': []} - result = callGAPI(gmail.users().settings().filters(), 'list', + result = gapi.call(gmail.users().settings().filters(), 'list', soft_errors=True, userId='me') jcount = len(result.get('filter', [])) if (result) else 0 @@ -6780,12 +6522,12 @@ def infoFilters(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - labels = callGAPI(gmail.users().labels(), 'list', + labels = gapi.call(gmail.users().labels(), 'list', soft_errors=True, userId='me', fields='labels(id,name)') if not labels: labels = {'labels': []} - result = callGAPI(gmail.users().settings().filters(), 'get', + result = gapi.call(gmail.users().settings().filters(), 'get', soft_errors=True, userId='me', id=filterId) if result: @@ -6807,9 +6549,9 @@ def doForward(users): body['emailAddress'] = sys.argv[i] i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam forward"' % myarg) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam forward"' % myarg) if enable and (not body.get('disposition') or not body.get('emailAddress')): - systemErrorExit(2, 'you must specify an action and a forwarding address for "gam forward') + controlflow.system_error_exit(2, 'you must specify an action and a forwarding address for "gam forward') i = 0 count = len(users) for user in users: @@ -6821,7 +6563,7 @@ def doForward(users): print("User: %s, Forward Enabled: %s, Forwarding Address: %s, Action: %s (%s/%s)" % (user, enable, body['emailAddress'], body['disposition'], i, count)) else: print("User: %s, Forward Enabled: %s (%s/%s)" % (user, enable, i, count)) - callGAPI(gmail.users().settings(), 'updateAutoForwarding', + gapi.call(gmail.users().settings(), 'updateAutoForwarding', soft_errors=True, userId='me', body=body) @@ -6864,7 +6606,7 @@ def _printForward(user, result): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s forward"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s forward"' % (myarg, ['show', 'print'][csvFormat])) i = 0 count = len(users) for user in users: @@ -6872,7 +6614,7 @@ def _printForward(user, result): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings(), 'getAutoForwarding', + result = gapi.call(gmail.users().settings(), 'getAutoForwarding', soft_errors=True, userId='me') if result: @@ -6894,7 +6636,7 @@ def addForwardingAddresses(users): if not gmail: continue print("Adding Forwarding Address %s for %s (%s/%s)" % (emailAddress, user, i, count)) - callGAPI(gmail.users().settings().forwardingAddresses(), 'create', + gapi.call(gmail.users().settings().forwardingAddresses(), 'create', soft_errors=True, userId='me', body=body) @@ -6908,7 +6650,7 @@ def deleteForwardingAddresses(users): if not gmail: continue print("Deleting Forwarding Address %s for %s (%s/%s)" % (emailAddress, user, i, count)) - callGAPI(gmail.users().settings().forwardingAddresses(), 'delete', + gapi.call(gmail.users().settings().forwardingAddresses(), 'delete', soft_errors=True, userId='me', forwardingEmail=emailAddress) @@ -6924,7 +6666,7 @@ def printShowForwardingAddresses(users, csvFormat): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s forwardingaddresses"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s forwardingaddresses"' % (myarg, ['show', 'print'][csvFormat])) i = 0 count = len(users) for user in users: @@ -6932,7 +6674,7 @@ def printShowForwardingAddresses(users, csvFormat): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings().forwardingAddresses(), 'list', + result = gapi.call(gmail.users().settings().forwardingAddresses(), 'list', soft_errors=True, userId='me') jcount = len(result.get('forwardingAddresses', [])) if (result) else 0 @@ -6962,7 +6704,7 @@ def infoForwardingAddresses(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - forward = callGAPI(gmail.users().settings().forwardingAddresses(), 'get', + forward = gapi.call(gmail.users().settings().forwardingAddresses(), 'get', soft_errors=True, userId='me', forwardingEmail=emailAddress) if forward: @@ -6996,7 +6738,7 @@ def doSignature(users): if not gmail: continue print('Setting Signature for {0} ({1}/{2})'.format(user, i, count)) - callGAPI(gmail.users().settings().sendAs(), 'patch', + gapi.call(gmail.users().settings().sendAs(), 'patch', soft_errors=True, userId='me', body=body, sendAsEmail=user) @@ -7009,7 +6751,7 @@ def getSignature(users): formatSig = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show signature"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show signature"' % sys.argv[i]) i = 0 count = len(users) for user in users: @@ -7017,7 +6759,7 @@ def getSignature(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings().sendAs(), 'get', + result = gapi.call(gmail.users().settings().sendAs(), 'get', soft_errors=True, userId='me', sendAsEmail=user) if result: @@ -7064,7 +6806,7 @@ def doVacation(users): body['endTime'] = getYYYYMMDD(sys.argv[i+1], returnTimeStamp=True) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam vacation"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam vacation"' % sys.argv[i]) if message: if responseBodyType == 'responseBodyHtml': message = message.replace('\r', '').replace('\\n', '
') @@ -7074,7 +6816,7 @@ def doVacation(users): message = _processTags(tagReplacements, message) body[responseBodyType] = message if not message and not body.get('responseSubject'): - systemErrorExit(2, 'You must specify a non-blank subject or message!') + controlflow.system_error_exit(2, 'You must specify a non-blank subject or message!') i = 0 count = len(users) for user in users: @@ -7083,7 +6825,7 @@ def doVacation(users): if not gmail: continue print("Setting Vacation for %s (%s/%s)" % (user, i, count)) - callGAPI(gmail.users().settings(), 'updateVacation', + gapi.call(gmail.users().settings(), 'updateVacation', soft_errors=True, userId='me', body=body) @@ -7096,7 +6838,7 @@ def getVacation(users): formatReply = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam show vacation"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show vacation"' % sys.argv[i]) i = 0 count = len(users) for user in users: @@ -7104,7 +6846,7 @@ def getVacation(users): user, gmail = buildGmailGAPIObject(user) if not gmail: continue - result = callGAPI(gmail.users().settings(), 'getVacation', + result = gapi.call(gmail.users().settings(), 'getVacation', soft_errors=True, userId='me') if result: @@ -7137,7 +6879,7 @@ def getVacation(users): def doDelSchema(): cd = buildGAPIObject('directory') schemaKey = sys.argv[3] - callGAPI(cd.schemas(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) + gapi.call(cd.schemas(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) print('Deleted schema %s' % schemaKey) def doCreateOrUpdateUserSchema(updateCmd): @@ -7146,9 +6888,9 @@ def doCreateOrUpdateUserSchema(updateCmd): if updateCmd: cmd = 'update' try: - body = callGAPI(cd.schemas(), 'get', throw_reasons=[GAPI_NOT_FOUND], customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) - except GAPI_notFound: - systemErrorExit(3, 'Schema %s does not exist.' % schemaKey) + body = gapi.call(cd.schemas(), 'get', throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND], customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) + except gapi.errors.GapiNotFoundError: + controlflow.system_error_exit(3, 'Schema %s does not exist.' % schemaKey) else: # create cmd = 'create' body = {'schemaName': schemaKey, 'fields': []} @@ -7168,7 +6910,7 @@ def doCreateOrUpdateUserSchema(updateCmd): if myarg == 'type': a_field['fieldType'] = sys.argv[i+1].upper() if a_field['fieldType'] not in ['BOOL', 'DOUBLE', 'EMAIL', 'INT64', 'PHONE', 'STRING']: - systemErrorExit(2, 'type must be one of bool, double, email, int64, phone, string; got %s' % a_field['fieldType']) + controlflow.system_error_exit(2, 'type must be one of bool, double, email, int64, phone, string; got %s' % a_field['fieldType']) i += 2 elif myarg == 'multivalued': a_field['multiValued'] = True @@ -7188,22 +6930,22 @@ def doCreateOrUpdateUserSchema(updateCmd): i += 1 break else: - systemErrorExit(2, '%s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd)) elif updateCmd and myarg == 'deletefield': for n, field in enumerate(body['fields']): if field['fieldName'].lower() == sys.argv[i+1].lower(): del body['fields'][n] break else: - systemErrorExit(3, 'field %s not found in schema %s' % (sys.argv[i+1], schemaKey)) + controlflow.system_error_exit(3, 'field %s not found in schema %s' % (sys.argv[i+1], schemaKey)) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd)) if updateCmd: - result = callGAPI(cd.schemas(), 'update', customerId=GC_Values[GC_CUSTOMER_ID], body=body, schemaKey=schemaKey) + result = gapi.call(cd.schemas(), 'update', customerId=GC_Values[GC_CUSTOMER_ID], body=body, schemaKey=schemaKey) print('Updated user schema %s' % result['schemaName']) else: - result = callGAPI(cd.schemas(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) + result = gapi.call(cd.schemas(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) print('Created user schema %s' % result['schemaName']) def _showSchema(schema): @@ -7230,8 +6972,8 @@ def doPrintShowUserSchemas(csvFormat): todrive = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s schemas"' % (myarg, ['show', 'print'][csvFormat])) - schemas = callGAPI(cd.schemas(), 'list', customerId=GC_Values[GC_CUSTOMER_ID]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s schemas"' % (myarg, ['show', 'print'][csvFormat])) + schemas = gapi.call(cd.schemas(), 'list', customerId=GC_Values[GC_CUSTOMER_ID]) if not schemas or 'schemas' not in schemas: return for schema in schemas['schemas']: @@ -7247,7 +6989,7 @@ def doPrintShowUserSchemas(csvFormat): def doGetUserSchema(): cd = buildGAPIObject('directory') schemaKey = sys.argv[3] - schema = callGAPI(cd.schemas(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) + schema = gapi.call(cd.schemas(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) _showSchema(schema) def getUserAttributes(i, cd, updateCmd): @@ -7294,7 +7036,7 @@ def appendItemToBodyList(body, itemName, itemValue, checkSystemId=False): for citem in body[itemName]: if citem.get('primary', False): if not checkSystemId or itemValue.get('systemId') == citem.get('systemId'): - systemErrorExit(2, 'Multiple {0} are marked primary, only one can be primary'.format(itemName)) + controlflow.system_error_exit(2, 'Multiple {0} are marked primary, only one can be primary'.format(itemName)) body[itemName].append(itemValue) def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): @@ -7308,7 +7050,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): schemaName = sn_fn.strip() if schemaName: return (schemaName, None) - systemErrorExit(2, '%s is not a valid custom schema.field name.' % sn_fn) + controlflow.system_error_exit(2, '%s is not a valid custom schema.field name.' % sn_fn) if updateCmd: body = {} @@ -7344,7 +7086,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): elif myarg == 'admin': value = getBoolean(sys.argv[i+1], myarg) if updateCmd or value: - systemErrorExit(2, '%s %s is not a valid argument for "gam %s user"' % (sys.argv[i], value, ['create', 'update'][updateCmd])) + controlflow.system_error_exit(2, '%s %s is not a valid argument for "gam %s user"' % (sys.argv[i], value, ['create', 'update'][updateCmd])) i += 2 elif myarg == 'suspended': body['suspended'] = getBoolean(sys.argv[i+1], myarg) @@ -7411,7 +7153,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): continue address = {} if sys.argv[i].lower() != 'type': - systemErrorExit(2, 'wrong format for account address details. Expected type got %s' % sys.argv[i]) + controlflow.system_error_exit(2, 'wrong format for account address details. Expected type got %s' % sys.argv[i]) i = getEntryType(i+1, address, USER_ADDRESS_TYPES) if sys.argv[i].lower() in ['unstructured', 'formatted']: i += 1 @@ -7449,7 +7191,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(2, 'invalid argument (%s) for account address details' % sys.argv[i]) + controlflow.system_error_exit(2, 'invalid argument (%s) for account address details' % sys.argv[i]) appendItemToBodyList(body, 'addresses', address) elif myarg in ['emails', 'otheremail', 'otheremails']: i += 1 @@ -7468,14 +7210,14 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): continue im = {} if sys.argv[i].lower() != 'type': - systemErrorExit(2, 'wrong format for account im details. Expected type got %s' % sys.argv[i]) + controlflow.system_error_exit(2, 'wrong format for account im details. Expected type got %s' % sys.argv[i]) i = getEntryType(i+1, im, USER_IM_TYPES) if sys.argv[i].lower() != 'protocol': - systemErrorExit(2, 'wrong format for account details. Expected protocol got %s' % sys.argv[i]) + controlflow.system_error_exit(2, 'wrong format for account details. Expected protocol got %s' % sys.argv[i]) i += 1 im['protocol'] = sys.argv[i].lower() if im['protocol'] not in ['custom_protocol', 'aim', 'gtalk', 'icq', 'jabber', 'msn', 'net_meeting', 'qq', 'skype', 'yahoo']: - systemErrorExit(2, 'protocol must be one of custom_protocol, aim, gtalk, icq, jabber, msn, net_meeting, qq, skype, yahoo; got %s' % im['protocol']) + controlflow.system_error_exit(2, 'protocol must be one of custom_protocol, aim, gtalk, icq, jabber, msn, net_meeting, qq, skype, yahoo; got %s' % im['protocol']) if im['protocol'] == 'custom_protocol': i += 1 im['customProtocol'] = sys.argv[i] @@ -7535,7 +7277,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(2, 'invalid argument (%s) for account organization details' % sys.argv[i]) + controlflow.system_error_exit(2, 'invalid argument (%s) for account organization details' % sys.argv[i]) appendItemToBodyList(body, 'organizations', organization) elif myarg in ['phone', 'phones']: i += 1 @@ -7555,7 +7297,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(2, 'invalid argument (%s) for account phone details' % sys.argv[i]) + controlflow.system_error_exit(2, 'invalid argument (%s) for account phone details' % sys.argv[i]) appendItemToBodyList(body, 'phones', phone) elif myarg in ['relation', 'relations']: i += 1 @@ -7637,7 +7379,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(3, '%s is not a valid argument for user location details. Make sure user location details end with an endlocation argument') + controlflow.system_error_exit(3, '%s is not a valid argument for user location details. Make sure user location details end with an endlocation argument') appendItemToBodyList(body, 'locations', location) elif myarg in ['ssh', 'sshkeys', 'sshpublickeys']: i += 1 @@ -7657,7 +7399,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(3, '%s is not a valid argument for user ssh details. Make sure user ssh details end with an endssh argument') + controlflow.system_error_exit(3, '%s is not a valid argument for user ssh details. Make sure user ssh details end with an endssh argument') appendItemToBodyList(body, 'sshPublicKeys', ssh) elif myarg in ['posix', 'posixaccounts']: i += 1 @@ -7698,7 +7440,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 break else: - systemErrorExit(3, '%s is not a valid argument for user posix details. Make sure user posix details end with an endposix argument') + controlflow.system_error_exit(3, '%s is not a valid argument for user posix details. Make sure user posix details end with an endposix argument') appendItemToBodyList(body, 'posixAccounts', posix, checkSystemId=True) elif myarg in ['keyword', 'keywords']: i += 1 @@ -7720,13 +7462,13 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 2 elif myarg == 'clearschema': if not updateCmd: - systemErrorExit(2, '%s is not a valid create user argument.' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid create user argument.' % sys.argv[i]) schemaName, fieldName = _splitSchemaNameDotFieldName(sys.argv[i+1], False) up = 'customSchemas' body.setdefault(up, {}) body[up].setdefault(schemaName, {}) if fieldName is None: - schema = callGAPI(cd.schemas(), 'get', + schema = gapi.call(cd.schemas(), 'get', soft_errors=True, customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaName, fields='fields(fieldName)') if not schema: @@ -7751,7 +7493,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): i += 1 schemaValue['type'] = sys.argv[i].lower() if schemaValue['type'] not in ['custom', 'home', 'other', 'work']: - systemErrorExit(2, 'wrong type must be one of custom, home, other, work; got %s' % schemaValue['type']) + controlflow.system_error_exit(2, 'wrong type must be one of custom, home, other, work; got %s' % schemaValue['type']) i += 1 if schemaValue['type'] == 'custom': schemaValue['customType'] = sys.argv[i] @@ -7763,7 +7505,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): body[up][schemaName][fieldName] = sys.argv[i] i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s user"' % (sys.argv[i], ['create', 'update'][updateCmd])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s user"' % (sys.argv[i], ['create', 'update'][updateCmd])) if need_password: rnd = SystemRandom() body['password'] = ''.join(rnd.choice(PASSWORD_SAFE_CHARS) for _ in range(100)) @@ -7775,7 +7517,7 @@ def _splitSchemaNameDotFieldName(sn_fn, fnRequired=True): class ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow): def authorization_url(self, **kwargs): long_url, state = super(ShortURLFlow, self).authorization_url(**kwargs) - simplehttp = _createHttpObj(timeout=10) + simplehttp = gapi.create_http(timeout=10) url_shortnr = 'https://gam-shortn.appspot.com/create' headers = {'Content-Type': 'application/json', 'user-agent': GAM_INFO} @@ -7836,7 +7578,7 @@ def getCRM2Service(httpc): def getGAMProjectFile(filepath): file_url = GAM_PROJECT_FILEPATH+filepath - httpObj = _createHttpObj() + httpObj = gapi.create_http() _, c = httpObj.request(file_url, 'GET') return c.decode(UTF8) @@ -7850,7 +7592,7 @@ def enableGAMProjectAPIs(GAMProjectAPIs, httpObj, projectId, checkEnabled, i=0, if checkEnabled: try: services = callGAPIpages(serveman.services(), 'list', 'services', - throw_reasons=[GAPI_NOT_FOUND], + throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND], consumerId=project_name, fields='nextPageToken,services(serviceName)') jcount = len(services) print(' Project: {0}, Check {1} APIs{2}'.format(projectId, jcount, currentCount(i, count))) @@ -7863,7 +7605,7 @@ def enableGAMProjectAPIs(GAMProjectAPIs, httpObj, projectId, checkEnabled, i=0, apis.remove(service['serviceName']) else: print(' API: {0}, Already enabled (non-GAM which is fine){1}'.format(service['serviceName'], currentCount(j, jcount))) - except GAPI_notFound as e: + except gapi.errors.GapiNotFoundError as e: print(' Project: {0}, Update Failed: {1}{2}'.format(projectId, str(e), currentCount(i, count))) status = False jcount = len(apis) @@ -7874,18 +7616,18 @@ def enableGAMProjectAPIs(GAMProjectAPIs, httpObj, projectId, checkEnabled, i=0, j += 1 while True: try: - callGAPI(serveman.services(), 'enable', - throw_reasons=[GAPI_FAILED_PRECONDITION, GAPI_FORBIDDEN, GAPI_PERMISSION_DENIED], + gapi.call(serveman.services(), 'enable', + throw_reasons=[gapi.errors.ErrorReason.FAILED_PRECONDITION, gapi.errors.ErrorReason.FORBIDDEN, gapi.errors.ErrorReason.PERMISSION_DENIED], serviceName=api, body={'consumerId': project_name}) print(' API: {0}, Enabled{1}'.format(api, currentCount(j, jcount))) break - except GAPI_failedPrecondition as e: + except gapi.errors.GapiFailedPreconditionError as e: print('\nThere was an error enabling %s. Please resolve error as described below:' % api) print() print('\n%s\n' % e) print() input('Press enter once resolved and we will try enabling the API again.') - except (GAPI_forbidden, GAPI_permissionDenied) as e: + except (gapi.errors.GapiForbiddenError, gapi.errors.GapiPermissionDeniedError) as e: print(' API: {0}, Enable Failed: {1}{2}'.format(api, str(e), currentCount(j, jcount))) status = False return status @@ -7923,7 +7665,7 @@ def _checkClientAndSecret(simplehttp, client_id, client_secret): iam = googleapiclient.discovery.build('iam', 'v1', http=httpObj, cache_discovery=False, discoveryServiceUrl=googleapiclient.discovery.V2_DISCOVERY_URI) - sa_list = callGAPI(iam.projects().serviceAccounts(), 'list', + sa_list = gapi.call(iam.projects().serviceAccounts(), 'list', name='projects/%s' % projectId) service_account = None if 'accounts' in sa_list: @@ -7934,10 +7676,10 @@ def _checkClientAndSecret(simplehttp, client_id, client_secret): break if not service_account: print('Creating Service Account') - service_account = callGAPI(iam.projects().serviceAccounts(), 'create', + service_account = gapi.call(iam.projects().serviceAccounts(), 'create', name='projects/%s' % projectId, body={'accountId': projectId, 'serviceAccount': {'displayName': 'GAM Project'}}) - key = callGAPI(iam.projects().serviceAccounts().keys(), 'create', + key = gapi.call(iam.projects().serviceAccounts().keys(), 'create', name=service_account['name'], body={'privateKeyType': 'TYPE_GOOGLE_CREDENTIALS_FILE', 'keyAlgorithm': 'KEY_ALG_RSA_2048'}) oauth2service_data = base64.b64decode(key['privateKeyData']).decode(UTF8) writeFile(GC_Values[GC_OAUTH2SERVICE_JSON], oauth2service_data, continueOnError=False) @@ -7963,7 +7705,7 @@ def _checkClientAndSecret(simplehttp, client_id, client_secret): client_secret = input('Enter your Client Secret: ').strip() if not client_secret: client_secret = input().strip() - simplehttp = _createHttpObj() + simplehttp = gapi.create_http() client_valid = _checkClientAndSecret(simplehttp, client_id, client_secret) if client_valid: break @@ -7998,17 +7740,17 @@ def _getValidateLoginHint(login_hint=None): def _getCurrentProjectID(): cs_data = readFile(GC_Values[GC_CLIENT_SECRETS_JSON], continueOnError=True, displayError=True) if not cs_data: - systemErrorExit(14, 'Your client secrets file:\n\n%s\n\nis missing. Please recreate the file.' % GC_Values[GC_CLIENT_SECRETS_JSON]) + controlflow.system_error_exit(14, 'Your client secrets file:\n\n%s\n\nis missing. Please recreate the file.' % GC_Values[GC_CLIENT_SECRETS_JSON]) try: return json.loads(cs_data)['installed']['project_id'] except (ValueError, IndexError, KeyError): - systemErrorExit(3, 'The format of your client secrets file:\n\n%s\n\nis incorrect. Please recreate the file.' % GC_Values[GC_CLIENT_SECRETS_JSON]) + controlflow.system_error_exit(3, 'The format of your client secrets file:\n\n%s\n\nis incorrect. Please recreate the file.' % GC_Values[GC_CLIENT_SECRETS_JSON]) def _getProjects(crm, pfilter): try: - return callGAPIpages(crm.projects(), 'list', 'projects', throw_reasons=[GAPI_BAD_REQUEST], filter=pfilter) - except GAPI_badRequest as e: - systemErrorExit(2, 'Project: {0}, {1}'.format(pfilter, str(e))) + return callGAPIpages(crm.projects(), 'list', 'projects', throw_reasons=[gapi.errors.ErrorReason.BAD_REQUEST], filter=pfilter) + except gapi.errors.GapiBadRequestError as e: + controlflow.system_error_exit(2, 'Project: {0}, {1}'.format(pfilter, str(e))) PROJECTID_PATTERN = re.compile(r'^[a-z][a-z0-9-]{4,28}[a-z0-9]$') PROJECTID_FORMAT_REQUIRED = '[a-z][a-z0-9-]{4,28}[a-z0-9]' @@ -8038,11 +7780,11 @@ def _getLoginHintProjectId(createCmd): parent = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam %s project", expected one of: admin, project%s' % (myarg, ['use', 'create'][createCmd], ['', ' or parent'][createCmd])) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam %s project", expected one of: admin, project%s' % (myarg, ['use', 'create'][createCmd], ['', ' or parent'][createCmd])) login_hint = _getValidateLoginHint(login_hint) if projectId: if not PROJECTID_PATTERN.match(projectId): - systemErrorExit(2, 'Invalid Project ID: {0}, expected <{1}>'.format(projectId, PROJECTID_FORMAT_REQUIRED)) + controlflow.system_error_exit(2, 'Invalid Project ID: {0}, expected <{1}>'.format(projectId, PROJECTID_FORMAT_REQUIRED)) elif createCmd: projectId = 'gam-project' for _ in range(3): @@ -8050,7 +7792,7 @@ def _getLoginHintProjectId(createCmd): else: projectId = input('\nWhat is your API project ID? ').strip() if not PROJECTID_PATTERN.match(projectId): - systemErrorExit(2, 'Invalid Project ID: {0}, expected <{1}>'.format(projectId, PROJECTID_FORMAT_REQUIRED)) + controlflow.system_error_exit(2, 'Invalid Project ID: {0}, expected <{1}>'.format(projectId, PROJECTID_FORMAT_REQUIRED)) crm, httpObj = getCRMService(login_hint) if parent and not parent.startswith('organizations/') and not parent.startswith('folders/'): crm2 = getCRM2Service(httpObj) @@ -8063,12 +7805,12 @@ def _getLoginHintProjectId(createCmd): projects = _getProjects(crm, 'id:{0}'.format(projectId)) if not createCmd: if not projects: - systemErrorExit(2, 'User: {0}, Project ID: {1}, Does not exist'.format(login_hint, projectId)) + controlflow.system_error_exit(2, 'User: {0}, Project ID: {1}, Does not exist'.format(login_hint, projectId)) if projects[0]['lifecycleState'] != 'ACTIVE': - systemErrorExit(2, 'User: {0}, Project ID: {1}, Not active'.format(login_hint, projectId)) + controlflow.system_error_exit(2, 'User: {0}, Project ID: {1}, Not active'.format(login_hint, projectId)) else: if projects: - systemErrorExit(2, 'User: {0}, Project ID: {1}, Duplicate'.format(login_hint, projectId)) + controlflow.system_error_exit(2, 'User: {0}, Project ID: {1}, Duplicate'.format(login_hint, projectId)) return (crm, httpObj, login_hint, projectId, parent) PROJECTID_FILTER_REQUIRED = 'gam||(filter )' @@ -8079,19 +7821,19 @@ def convertGCPFolderNameToID(parent, crm2): folders = callGAPIitems(crm2.folders(), 'search', items='folders', body={'pageSize': 1000, 'query': 'displayName="%s"' % parent}) if not folders: - systemErrorExit(1, 'ERROR: No folder found matching displayName=%s' % parent) + controlflow.system_error_exit(1, 'ERROR: No folder found matching displayName=%s' % parent) if len(folders) > 1: print('Multiple matches:') for folder in folders: print(' Name: %s ID: %s' % (folder['name'], folder['displayName'])) - systemErrorExit(2, 'ERROR: Multiple matching folders, please specify one.') + controlflow.system_error_exit(2, 'ERROR: Multiple matching folders, please specify one.') return folders[0]['name'] def createGCPFolder(): login_hint = _getValidateLoginHint() _, httpObj = getCRMService(login_hint) crm2 = getCRM2Service(httpObj) - callGAPI(crm2.folders(), 'create', body={'name': sys.argv[3], 'displayName': sys.argv[3]}) + gapi.call(crm2.folders(), 'create', body={'name': sys.argv[3], 'displayName': sys.argv[3]}) def _getLoginHintProjects(printShowCmd): login_hint = None @@ -8116,7 +7858,7 @@ def _getLoginHintProjects(printShowCmd): elif PROJECTID_PATTERN.match(pfilter): pfilter = 'id:{0}'.format(pfilter) else: - systemErrorExit(2, 'Invalid Project ID: {0}, expected <{1}{2}>'.format(pfilter, ['', 'all|'][printShowCmd], PROJECTID_FILTER_REQUIRED)) + controlflow.system_error_exit(2, 'Invalid Project ID: {0}, expected <{1}{2}>'.format(pfilter, ['', 'all|'][printShowCmd], PROJECTID_FILTER_REQUIRED)) login_hint = _getValidateLoginHint(login_hint) crm, httpObj = getCRMService(login_hint) if pfilter in ['current', 'id:current']: @@ -8132,7 +7874,7 @@ def _getLoginHintProjects(printShowCmd): def _checkForExistingProjectFiles(): for a_file in [GC_Values[GC_OAUTH2SERVICE_JSON], GC_Values[GC_CLIENT_SECRETS_JSON]]: if os.path.exists(a_file): - systemErrorExit(5, '%s already exists. Please delete or rename it before attempting to use another project.' % a_file) + controlflow.system_error_exit(5, '%s already exists. Please delete or rename it before attempting to use another project.' % a_file) def doCreateProject(): _checkForExistingProjectFiles() @@ -8144,25 +7886,25 @@ def doCreateProject(): while True: create_again = False print('Creating project "%s"...' % body['name']) - create_operation = callGAPI(crm.projects(), 'create', body=body) + create_operation = gapi.call(crm.projects(), 'create', body=body) operation_name = create_operation['name'] time.sleep(8) # Google recommends always waiting at least 5 seconds for i in range(1, 5): print('Checking project status...') - status = callGAPI(crm.operations(), 'get', + status = gapi.call(crm.operations(), 'get', name=operation_name) if 'error' in status: if status['error'].get('message', '') == 'No permission to create project in organization': print('Hmm... Looks like you have no rights to your Google Cloud Organization.') print('Attempting to fix that...') - getorg = callGAPI(crm.organizations(), 'search', + getorg = gapi.call(crm.organizations(), 'search', body={'filter': 'domain:%s' % login_domain}) try: organization = getorg['organizations'][0]['name'] print('Your organization name is %s' % organization) except (KeyError, IndexError): - systemErrorExit(3, 'you have no rights to create projects for your organization and you don\'t seem to be a super admin! Sorry, there\'s nothing more I can do.') - org_policy = callGAPI(crm.organizations(), 'getIamPolicy', + controlflow.system_error_exit(3, 'you have no rights to create projects for your organization and you don\'t seem to be a super admin! Sorry, there\'s nothing more I can do.') + org_policy = gapi.call(crm.organizations(), 'getIamPolicy', resource=organization) if 'bindings' not in org_policy: org_policy['bindings'] = [] @@ -8180,7 +7922,7 @@ def doCreateProject(): my_role = 'roles/resourcemanager.projectCreator' print('Giving %s the role of %s...' % (login_hint, my_role)) org_policy['bindings'].append({'role': my_role, 'members': ['user:%s' % login_hint]}) - callGAPI(crm.organizations(), 'setIamPolicy', + gapi.call(crm.organizations(), 'setIamPolicy', resource=organization, body={'policy': org_policy}) create_again = True break @@ -8196,7 +7938,7 @@ def doCreateProject(): break except (IndexError, KeyError): pass - systemErrorExit(1, status) + controlflow.system_error_exit(1, status) if status.get('done', False): break sleep_time = i ** 2 @@ -8205,9 +7947,9 @@ def doCreateProject(): if create_again: continue if not status.get('done', False): - systemErrorExit(1, 'Failed to create project: %s' % status) + controlflow.system_error_exit(1, 'Failed to create project: %s' % status) elif 'error' in status: - systemErrorExit(2, status['error']) + controlflow.system_error_exit(2, status['error']) break _createClientSecretsOauth2service(httpObj, projectId) @@ -8236,9 +7978,9 @@ def doDelProjects(): i += 1 projectId = project['projectId'] try: - callGAPI(crm.projects(), 'delete', throw_reasons=[GAPI_FORBIDDEN], projectId=projectId) + gapi.call(crm.projects(), 'delete', throw_reasons=[gapi.errors.ErrorReason.FORBIDDEN], projectId=projectId) print(' Project: {0} Deleted{1}'.format(projectId, currentCount(i, count))) - except GAPI_forbidden as e: + except gapi.errors.GapiForbiddenError as e: print(' Project: {0} Delete Failed: {1}{2}'.format(projectId, str(e), currentCount(i, count))) @@ -8254,7 +7996,7 @@ def doPrintShowProjects(csvFormat): todrive = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument for "gam %s projects"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam %s projects"' % (myarg, ['show', 'print'][csvFormat])) if not csvFormat: count = len(projects) print('User: {0}, Show {1} Projects'.format(login_hint, count)) @@ -8290,13 +8032,13 @@ def doGetTeamDriveInfo(users): useDomainAdminAccess = True i += 1 else: - systemErrorExit(3, '%s is not a valid command for "gam show teamdrive"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid command for "gam show teamdrive"' % sys.argv[i]) for user in users: drive = buildGAPIServiceObject('drive3', user) if not drive: print('Failed to access Drive as %s' % user) continue - result = callGAPI(drive.drives(), 'get', driveId=teamDriveId, + result = gapi.call(drive.drives(), 'get', driveId=teamDriveId, useDomainAdminAccess=useDomainAdminAccess, fields='*') print_json(None, result) @@ -8309,14 +8051,14 @@ def doCreateTeamDrive(users): body['themeId'] = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam create teamdrive"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam create teamdrive"' % sys.argv[i]) for user in users: drive = buildGAPIServiceObject('drive3', user) if not drive: print('Failed to access Drive as %s' % user) continue requestId = str(uuid.uuid4()) - result = callGAPI(drive.drives(), 'create', requestId=requestId, body=body, fields='id') + result = gapi.call(drive.drives(), 'create', requestId=requestId, body=body, fields='id') print('Created Team Drive %s with id %s' % (body['name'], result['id'])) TEAMDRIVE_RESTRICTIONS_MAP = { @@ -8358,14 +8100,14 @@ def doUpdateTeamDrive(users): body['restrictions'][TEAMDRIVE_RESTRICTIONS_MAP[myarg]] = getBoolean(sys.argv[i+1], myarg) i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam update teamdrive"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam update teamdrive"' % sys.argv[i]) if not body: - systemErrorExit(4, 'nothing to update. Need at least a name argument.') + controlflow.system_error_exit(4, 'nothing to update. Need at least a name argument.') for user in users: user, drive = buildDrive3GAPIObject(user) if not drive: continue - result = callGAPI(drive.drives(), 'update', + result = gapi.call(drive.drives(), 'update', useDomainAdminAccess=useDomainAdminAccess, body=body, driveId=teamDriveId, fields='id', soft_errors=True) if not result: continue @@ -8388,7 +8130,7 @@ def printShowTeamDrives(users, csvFormat): q = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam print|show teamdrives"') + controlflow.system_error_exit(3, '%s is not a valid argument for "gam print|show teamdrives"') tds = [] for user in users: sys.stderr.write('Getting Team Drives for %s\n' % user) @@ -8423,14 +8165,14 @@ def doDeleteTeamDrive(users): if not drive: continue print('Deleting Team Drive %s' % (teamDriveId)) - callGAPI(drive.drives(), 'delete', driveId=teamDriveId, soft_errors=True) + gapi.call(drive.drives(), 'delete', driveId=teamDriveId, soft_errors=True) def validateCollaborators(collaboratorList, cd): collaborators = [] for collaborator in collaboratorList.split(','): collaborator_id = convertEmailAddressToUID(collaborator, cd) if not collaborator_id: - systemErrorExit(4, 'failed to get a UID for %s. Please make sure this is a real user.' % collaborator) + controlflow.system_error_exit(4, 'failed to get a UID for %s. Please make sure this is a real user.' % collaborator) collaborators.append({'email': collaborator, 'id': collaborator_id}) return collaborators @@ -8454,12 +8196,12 @@ def doCreateVaultMatter(): collaborators.extend(validateCollaborators(sys.argv[i+1], cd)) i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam create matter"' % sys.argv[i]) - matterId = callGAPI(v.matters(), 'create', body=body, fields='matterId')['matterId'] + controlflow.system_error_exit(3, '%s is not a valid argument to "gam create matter"' % sys.argv[i]) + matterId = gapi.call(v.matters(), 'create', body=body, fields='matterId')['matterId'] print('Created matter %s' % matterId) for collaborator in collaborators: print(' adding collaborator %s' % collaborator['email']) - callGAPI(v.matters(), 'addPermissions', matterId=matterId, body={'matterPermission': {'role': 'COLLABORATOR', 'accountId': collaborator['id']}}) + gapi.call(v.matters(), 'addPermissions', matterId=matterId, body={'matterPermission': {'role': 'COLLABORATOR', 'accountId': collaborator['id']}}) VAULT_SEARCH_METHODS_MAP = { 'account': 'ACCOUNT', @@ -8499,11 +8241,11 @@ def doCreateVaultExport(): elif myarg == 'corpus': body['query']['corpus'] = sys.argv[i+1].upper() if body['query']['corpus'] not in allowed_corpuses: - systemErrorExit(3, 'corpus must be one of %s. Got %s' % (', '.join(allowed_corpuses), sys.argv[i+1])) + controlflow.system_error_exit(3, 'corpus must be one of %s. Got %s' % (', '.join(allowed_corpuses), sys.argv[i+1])) i += 2 elif myarg in VAULT_SEARCH_METHODS_MAP: if body['query'].get('searchMethod'): - systemErrorExit(3, 'Multiple search methods ({0}) specified, only one is allowed'.format(', '.join(VAULT_SEARCH_METHODS_LIST))) + controlflow.system_error_exit(3, 'Multiple search methods ({0}) specified, only one is allowed'.format(', '.join(VAULT_SEARCH_METHODS_LIST))) searchMethod = VAULT_SEARCH_METHODS_MAP[myarg] body['query']['searchMethod'] = searchMethod if searchMethod == 'ACCOUNT': @@ -8523,7 +8265,7 @@ def doCreateVaultExport(): elif myarg == 'scope': body['query']['dataScope'] = sys.argv[i+1].upper() if body['query']['dataScope'] not in allowed_scopes: - systemErrorExit(3, 'scope must be one of %s. Got %s' % (', '.join(allowed_scopes), sys.argv[i+1])) + controlflow.system_error_exit(3, 'scope must be one of %s. Got %s' % (', '.join(allowed_scopes), sys.argv[i+1])) i += 2 elif myarg in ['terms']: body['query']['terms'] = sys.argv[i+1] @@ -8562,19 +8304,19 @@ def doCreateVaultExport(): allowed_regions = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['ExportOptions']['properties']['region']['enum']) body['exportOptions']['region'] = sys.argv[i+1].upper() if body['exportOptions']['region'] not in allowed_regions: - systemErrorExit(3, 'region should be one of %s, got %s' % (', '.join(allowed_regions), body['exportOptions']['region'])) + controlflow.system_error_exit(3, 'region should be one of %s, got %s' % (', '.join(allowed_regions), body['exportOptions']['region'])) i += 2 elif myarg in ['includeaccessinfo']: body['exportOptions'].setdefault('driveOptions', {})['includeAccessInfo'] = getBoolean(sys.argv[i+1], myarg) i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam create export"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam create export"' % sys.argv[i]) if not matterId: - systemErrorExit(3, 'you must specify a matter for the new export.') + controlflow.system_error_exit(3, 'you must specify a matter for the new export.') if 'corpus' not in body['query']: - systemErrorExit(3, 'you must specify a corpus for the new export. Choose one of %s' % ', '.join(allowed_corpuses)) + controlflow.system_error_exit(3, 'you must specify a corpus for the new export. Choose one of %s' % ', '.join(allowed_corpuses)) if 'searchMethod' not in body['query']: - systemErrorExit(3, 'you must specify a search method for the new export. Choose one of %s' % ', '.join(VAULT_SEARCH_METHODS_LIST)) + controlflow.system_error_exit(3, 'you must specify a search method for the new export. Choose one of %s' % ', '.join(VAULT_SEARCH_METHODS_LIST)) if 'name' not in body: body['name'] = 'GAM %s export - %s' % (body['query']['corpus'], datetime.datetime.now()) options_field = None @@ -8589,7 +8331,7 @@ def doCreateVaultExport(): body['exportOptions'][options_field] = {'exportFormat': export_format} if showConfidentialModeContent is not None: body['exportOptions'][options_field]['showConfidentialModeContent'] = showConfidentialModeContent - results = callGAPI(v.matters().exports(), 'create', matterId=matterId, body=body) + results = gapi.call(v.matters().exports(), 'create', matterId=matterId, body=body) print('Created export %s' % results['id']) print_json(None, results) @@ -8598,13 +8340,13 @@ def doDeleteVaultExport(): matterId = getMatterItem(v, sys.argv[3]) exportId = convertExportNameToID(v, sys.argv[4], matterId) print('Deleting export %s / %s' % (sys.argv[4], exportId)) - callGAPI(v.matters().exports(), 'delete', matterId=matterId, exportId=exportId) + gapi.call(v.matters().exports(), 'delete', matterId=matterId, exportId=exportId) def doGetVaultExportInfo(): v = buildGAPIObject('vault') matterId = getMatterItem(v, sys.argv[3]) exportId = convertExportNameToID(v, sys.argv[4], matterId) - export = callGAPI(v.matters().exports(), 'get', matterId=matterId, exportId=exportId) + export = gapi.call(v.matters().exports(), 'get', matterId=matterId, exportId=exportId) print_json(None, export) def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None): @@ -8655,7 +8397,7 @@ def md5MatchesFile(local_file, expected_md5, exitOnError): hash_md5.update(chunk) actual_hash = hash_md5.hexdigest() if exitOnError and actual_hash != expected_md5: - systemErrorExit(6, 'actual hash was %s. Exiting on corrupt file.' % actual_hash) + controlflow.system_error_exit(6, 'actual hash was %s. Exiting on corrupt file.' % actual_hash) return actual_hash == expected_md5 def doDownloadCloudStorageBucket(): @@ -8665,7 +8407,7 @@ def doDownloadCloudStorageBucket(): if bucket_match: bucket = bucket_match.group(1) else: - systemErrorExit(5, 'Could not find a takeout-export-* bucket in that URL') + controlflow.system_error_exit(5, 'Could not find a takeout-export-* bucket in that URL') s = buildGAPIObject('storage') page_message = 'Got %%total_items%% files...' objects = callGAPIpages(s.objects(), 'list', 'items', page_message=page_message, bucket=bucket, projection='noAcl', fields='nextPageToken,items(name,id,md5Hash)') @@ -8699,8 +8441,8 @@ def doDownloadVaultExport(): extractFiles = False i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam download export"' % sys.argv[i]) - export = callGAPI(v.matters().exports(), 'get', matterId=matterId, exportId=exportId) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam download export"' % sys.argv[i]) + export = gapi.call(v.matters().exports(), 'get', matterId=matterId, exportId=exportId) for s_file in export['cloudStorageSink']['files']: bucket = s_file['bucketName'] s_object = s_file['objectName'] @@ -8764,7 +8506,7 @@ def doCreateVaultHold(): elif myarg == 'corpus': body['corpus'] = sys.argv[i+1].upper() if body['corpus'] not in allowed_corpuses: - systemErrorExit(3, 'corpus must be one of %s. Got %s' % (', '.join(allowed_corpuses), sys.argv[i+1])) + controlflow.system_error_exit(3, 'corpus must be one of %s. Got %s' % (', '.join(allowed_corpuses), sys.argv[i+1])) i += 2 elif myarg in ['accounts', 'users', 'groups']: accounts = sys.argv[i+1].split(',') @@ -8782,13 +8524,13 @@ def doCreateVaultHold(): matterId = getMatterItem(v, sys.argv[i+1]) i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam create hold"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam create hold"' % sys.argv[i]) if not matterId: - systemErrorExit(3, 'you must specify a matter for the new hold.') + controlflow.system_error_exit(3, 'you must specify a matter for the new hold.') if not body.get('name'): - systemErrorExit(3, 'you must specify a name for the new hold.') + controlflow.system_error_exit(3, 'you must specify a name for the new hold.') if not body.get('corpus'): - systemErrorExit(3, 'you must specify a corpus for the new hold. Choose one of %s' % (', '.join(allowed_corpuses))) + controlflow.system_error_exit(3, 'you must specify a corpus for the new hold. Choose one of %s' % (', '.join(allowed_corpuses))) if body['corpus'] == 'HANGOUTS_CHAT': query_type = 'hangoutsChatQuery' else: @@ -8799,7 +8541,7 @@ def doCreateVaultHold(): try: body['query'][query_type] = json.loads(query) except ValueError as e: - systemErrorExit(3, '{0}, query: {1}'.format(str(e), query)) + controlflow.system_error_exit(3, '{0}, query: {1}'.format(str(e), query)) elif body['corpus'] in ['GROUPS', 'MAIL']: if query: body['query'][query_type] = {'terms': query} @@ -8813,7 +8555,7 @@ def doCreateVaultHold(): account_type = 'group' if body['corpus'] == 'GROUPS' else 'user' for account in accounts: body['accounts'].append({'accountId': convertEmailAddressToUID(account, cd, account_type)}) - holdId = callGAPI(v.matters().holds(), 'create', matterId=matterId, body=body, fields='holdId')['holdId'] + holdId = gapi.call(v.matters().holds(), 'create', matterId=matterId, body=body, fields='holdId')['holdId'] print('Created hold %s' % holdId) def doDeleteVaultHold(): @@ -8828,11 +8570,11 @@ def doDeleteVaultHold(): holdId = convertHoldNameToID(v, hold, matterId) i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam delete hold"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam delete hold"' % myarg) if not matterId: - systemErrorExit(3, 'you must specify a matter for the hold.') + controlflow.system_error_exit(3, 'you must specify a matter for the hold.') print('Deleting hold %s / %s' % (hold, holdId)) - callGAPI(v.matters().holds(), 'delete', matterId=matterId, holdId=holdId) + gapi.call(v.matters().holds(), 'delete', matterId=matterId, holdId=holdId) def doGetVaultHoldInfo(): v = buildGAPIObject('vault') @@ -8846,10 +8588,10 @@ def doGetVaultHoldInfo(): holdId = convertHoldNameToID(v, hold, matterId) i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam info hold"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam info hold"' % myarg) if not matterId: - systemErrorExit(3, 'you must specify a matter for the hold.') - results = callGAPI(v.matters().holds(), 'get', matterId=matterId, holdId=holdId) + 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 = buildGAPIObject('directory') if 'accounts' in results: account_type = 'group' if results['corpus'] == 'GROUPS' else 'user' @@ -8870,7 +8612,7 @@ def convertExportNameToID(v, nameOrID, matterId): for export in exports: if export['name'].lower() == nameOrID: return export['id'] - systemErrorExit(4, 'could not find export name %s in matter %s' % (nameOrID, matterId)) + controlflow.system_error_exit(4, 'could not find export name %s in matter %s' % (nameOrID, matterId)) def convertHoldNameToID(v, nameOrID, matterId): nameOrID = nameOrID.lower() @@ -8881,7 +8623,7 @@ def convertHoldNameToID(v, nameOrID, matterId): for hold in holds: if hold['name'].lower() == nameOrID: return hold['holdId'] - systemErrorExit(4, 'could not find hold name %s in matter %s' % (nameOrID, matterId)) + controlflow.system_error_exit(4, 'could not find hold name %s in matter %s' % (nameOrID, matterId)) def convertMatterNameToID(v, nameOrID): nameOrID = nameOrID.lower() @@ -8897,7 +8639,7 @@ def convertMatterNameToID(v, nameOrID): def getMatterItem(v, nameOrID): matterId = convertMatterNameToID(v, nameOrID) if not matterId: - systemErrorExit(4, 'could not find matter %s' % nameOrID) + controlflow.system_error_exit(4, 'could not find matter %s' % nameOrID) return matterId def doUpdateVaultHold(): @@ -8936,11 +8678,11 @@ def doUpdateVaultHold(): del_accounts = sys.argv[i+1].split(',') i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam update hold"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam update hold"' % myarg) if not matterId: - systemErrorExit(3, 'you must specify a matter for the hold.') + 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'): - old_body = callGAPI(v.matters().holds(), 'get', matterId=matterId, holdId=holdId, fields='corpus,query,orgUnit') + old_body = gapi.call(v.matters().holds(), 'get', matterId=matterId, holdId=holdId, fields='corpus,query,orgUnit') body['query'] = old_body['query'] body['corpus'] = old_body['corpus'] if 'orgUnit' in old_body and 'orgUnit' not in body: @@ -8952,7 +8694,7 @@ def doUpdateVaultHold(): try: body['query'][query_type] = json.loads(query) except ValueError as e: - systemErrorExit(3, '{0}, query: {1}'.format(str(e), query)) + controlflow.system_error_exit(3, '{0}, query: {1}'.format(str(e), query)) elif body['corpus'] in ['GROUPS', 'MAIL']: if query: body['query'][query_type]['terms'] = query @@ -8962,17 +8704,17 @@ def doUpdateVaultHold(): body['query'][query_type]['endTime'] = end_time if body: print('Updating hold %s / %s' % (hold, holdId)) - callGAPI(v.matters().holds(), 'update', matterId=matterId, holdId=holdId, body=body) + gapi.call(v.matters().holds(), 'update', matterId=matterId, holdId=holdId, body=body) if add_accounts or del_accounts: cd = buildGAPIObject('directory') for account in add_accounts: print('adding %s to hold.' % account) add_body = {'accountId': convertEmailAddressToUID(account, cd)} - callGAPI(v.matters().holds().accounts(), 'create', matterId=matterId, holdId=holdId, body=add_body) + gapi.call(v.matters().holds().accounts(), 'create', matterId=matterId, holdId=holdId, body=add_body) for account in del_accounts: print('removing %s from hold.' % account) accountId = convertEmailAddressToUID(account, cd) - callGAPI(v.matters().holds().accounts(), 'delete', matterId=matterId, holdId=holdId, accountId=accountId) + gapi.call(v.matters().holds().accounts(), 'delete', matterId=matterId, holdId=holdId, accountId=accountId) def doUpdateVaultMatter(action=None): v = buildGAPIObject('vault') @@ -8988,7 +8730,7 @@ def doUpdateVaultMatter(action=None): if myarg == 'action': action = sys.argv[i+1].lower() if action not in VAULT_MATTER_ACTIONS: - systemErrorExit(3, 'allowed actions are %s, got %s' % (', '.join(VAULT_MATTER_ACTIONS), action)) + controlflow.system_error_exit(3, 'allowed actions are %s, got %s' % (', '.join(VAULT_MATTER_ACTIONS), action)) i += 2 elif myarg == 'name': body['name'] = sys.argv[i+1] @@ -9007,31 +8749,31 @@ def doUpdateVaultMatter(action=None): remove_collaborators.extend(validateCollaborators(sys.argv[i+1], cd)) i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam update matter"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam update matter"' % sys.argv[i]) if action == 'delete': action_kwargs = {} if body: print('Updating matter %s...' % 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 = callGAPI(v.matters(), 'get', matterId=matterId, view='BASIC') + result = gapi.call(v.matters(), 'get', matterId=matterId, view='BASIC') body.setdefault('name', result['name']) body.setdefault('description', result.get('description')) - callGAPI(v.matters(), 'update', body=body, matterId=matterId) + gapi.call(v.matters(), 'update', body=body, matterId=matterId) if action: print('Performing %s on matter %s' % (action, sys.argv[3])) - callGAPI(v.matters(), action, matterId=matterId, **action_kwargs) + gapi.call(v.matters(), action, matterId=matterId, **action_kwargs) for collaborator in add_collaborators: print(' adding collaborator %s' % collaborator['email']) - callGAPI(v.matters(), 'addPermissions', matterId=matterId, body={'matterPermission': {'role': 'COLLABORATOR', 'accountId': collaborator['id']}}) + gapi.call(v.matters(), 'addPermissions', matterId=matterId, body={'matterPermission': {'role': 'COLLABORATOR', 'accountId': collaborator['id']}}) for collaborator in remove_collaborators: print(' removing collaborator %s' % collaborator['email']) - callGAPI(v.matters(), 'removePermissions', matterId=matterId, body={'accountId': collaborator['id']}) + gapi.call(v.matters(), 'removePermissions', matterId=matterId, body={'accountId': collaborator['id']}) def doGetVaultMatterInfo(): v = buildGAPIObject('vault') matterId = getMatterItem(v, sys.argv[3]) - result = callGAPI(v.matters(), 'get', matterId=matterId, view='FULL') + result = gapi.call(v.matters(), 'get', matterId=matterId, view='FULL') if 'matterPermissions' in result: cd = buildGAPIObject('directory') for i in range(0, len(result['matterPermissions'])): @@ -9044,7 +8786,7 @@ def doCreateUser(): cd = buildGAPIObject('directory') body = getUserAttributes(3, cd, False) print("Creating account for %s" % body['primaryEmail']) - callGAPI(cd.users(), 'insert', body=body, fields='primaryEmail') + gapi.call(cd.users(), 'insert', body=body, fields='primaryEmail') def GroupIsAbuseOrPostmaster(emailAddr): return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@') @@ -9069,7 +8811,7 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function): else: value = int(value) except ValueError: - systemErrorExit(2, '%s must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got %s' % value) + controlflow.system_error_exit(2, '%s must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got %s' % value) elif params['type'] == 'string': if attrib == 'description': value = value.replace('\\n', '\n') @@ -9079,7 +8821,7 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function): value = value.upper() possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(params['description']) if value not in possible_values: - systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value)) + controlflow.system_error_exit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value)) elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES: value = value.lower() if value in true_values: @@ -9087,10 +8829,10 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function): elif value in false_values: value = 'false' else: - systemErrorExit(2, 'value for %s must be true|false. Got %s.' % (attrib, value)) + controlflow.system_error_exit(2, 'value for %s must be true|false. Got %s.' % (attrib, value)) gs_body[attrib] = value return - systemErrorExit(2, '%s is not a valid argument for "gam %s group"' % (myarg, function)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s group"' % (myarg, function)) def doCreateGroup(): cd = buildGAPIObject('directory') @@ -9131,34 +8873,34 @@ def doCreateGroup(): if not got_name: body['name'] = body['email'] print("Creating group %s" % body['email']) - callGAPI(cd.groups(), 'insert', body=body, fields='email') + gapi.call(cd.groups(), 'insert', body=body, fields='email') if gs and not GroupIsAbuseOrPostmaster(body['email']): if gs_get_before_update: - current_settings = callGAPI(gs.groups(), 'get', + current_settings = gapi.call(gs.groups(), 'get', retry_reasons=['serviceLimit', 'notFound'], groupUniqueId=body['email'], fields='*') if current_settings is not None: gs_body = dict(list(current_settings.items()) + list(gs_body.items())) if gs_body: - callGAPI(gs.groups(), 'update', retry_reasons=['serviceLimit', 'notFound'], groupUniqueId=body['email'], body=gs_body) + gapi.call(gs.groups(), 'update', retry_reasons=['serviceLimit', 'notFound'], groupUniqueId=body['email'], body=gs_body) def doCreateAlias(): cd = buildGAPIObject('directory') body = {'alias': normalizeEmailAddressOrUID(sys.argv[3], noUid=True, noLower=True)} target_type = sys.argv[4].lower() if target_type not in ['user', 'group', 'target']: - systemErrorExit(2, 'type of target must be user or group; got %s' % target_type) + controlflow.system_error_exit(2, 'type of target must be user or group; got %s' % target_type) targetKey = normalizeEmailAddressOrUID(sys.argv[5]) print('Creating alias %s for %s %s' % (body['alias'], target_type, targetKey)) if target_type == 'user': - callGAPI(cd.users().aliases(), 'insert', userKey=targetKey, body=body) + gapi.call(cd.users().aliases(), 'insert', userKey=targetKey, body=body) elif target_type == 'group': - callGAPI(cd.groups().aliases(), 'insert', groupKey=targetKey, body=body) + gapi.call(cd.groups().aliases(), 'insert', groupKey=targetKey, body=body) elif target_type == 'target': try: - callGAPI(cd.users().aliases(), 'insert', throw_reasons=[GAPI_INVALID, GAPI_BAD_REQUEST], userKey=targetKey, body=body) - except (GAPI_invalid, GAPI_badRequest): - callGAPI(cd.groups().aliases(), 'insert', groupKey=targetKey, body=body) + gapi.call(cd.users().aliases(), 'insert', throw_reasons=[gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST], userKey=targetKey, body=body) + except (gapi.errors.GapiInvalidError, gapi.errors.GapiBadRequestError): + gapi.call(cd.groups().aliases(), 'insert', groupKey=targetKey, body=body) def doCreateOrg(): cd = buildGAPIObject('directory') @@ -9181,9 +8923,9 @@ def doCreateOrg(): body['blockInheritance'] = False i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam create org"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam create org"' % sys.argv[i]) if parent.startswith('id:'): - parent = callGAPI(cd.orgunits(), 'get', + parent = gapi.call(cd.orgunits(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=parent, fields='orgUnitPath')['orgUnitPath'] if parent == '/': orgUnitPath = parent+name @@ -9195,7 +8937,7 @@ def doCreateOrg(): body['parentOrgUnitPath'] = '/' body['name'] = orgUnitPath[1:] parent = body['parentOrgUnitPath'] - callGAPI(cd.orgunits(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) + gapi.call(cd.orgunits(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) def _getBuildingAttributes(args, body={}): i = 0 @@ -9224,7 +8966,7 @@ def _getBuildingAttributes(args, body={}): body['floorNames'] = args[i+1].split(',') i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam create|update building"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam create|update building"' % myarg) return body def doCreateBuilding(): @@ -9234,7 +8976,7 @@ def doCreateBuilding(): 'buildingName': sys.argv[3]} body = _getBuildingAttributes(sys.argv[4:], body) print('Creating building %s...' % body['buildingId']) - callGAPI(cd.resources().buildings(), 'insert', + gapi.call(cd.resources().buildings(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def _makeBuildingIdNameMap(cd): @@ -9251,7 +8993,7 @@ def _getBuildingByNameOrId(cd, which_building, minLen=1): if not which_building or (minLen == 0 and which_building in ['id:', 'uid:']): if minLen == 0: return '' - systemErrorExit(3, 'Building id/name is empty') + controlflow.system_error_exit(3, 'Building id/name is empty') cg = UID_PATTERN.match(which_building) if cg: return cg.group(1) @@ -9284,10 +9026,10 @@ def _getBuildingByNameOrId(cd, which_building, minLen=1): for building in ci_matches: message += ' Name:%s id:%s\n' % (building['buildingName'], building['buildingId']) message += '\nPlease specify building name by exact case or by id.' - systemErrorExit(3, message) + controlflow.system_error_exit(3, message) # No matches else: - systemErrorExit(3, 'No such building %s' % which_building) + controlflow.system_error_exit(3, 'No such building %s' % which_building) def _getBuildingNameById(cd, buildingId): if GM_Globals[GM_MAP_BUILDING_ID_TO_NAME] is None: @@ -9299,13 +9041,13 @@ def doUpdateBuilding(): buildingId = _getBuildingByNameOrId(cd, sys.argv[3]) body = _getBuildingAttributes(sys.argv[4:]) print('Updating building %s...' % buildingId) - callGAPI(cd.resources().buildings(), 'patch', + gapi.call(cd.resources().buildings(), 'patch', customer=GC_Values[GC_CUSTOMER_ID], buildingId=buildingId, body=body) def doGetBuildingInfo(): cd = buildGAPIObject('directory') buildingId = _getBuildingByNameOrId(cd, sys.argv[3]) - building = callGAPI(cd.resources().buildings(), 'get', + building = gapi.call(cd.resources().buildings(), 'get', customer=GC_Values[GC_CUSTOMER_ID], buildingId=buildingId) if 'buildingId' in building: building['buildingId'] = 'id:{0}'.format(building['buildingId']) @@ -9319,7 +9061,7 @@ def doDeleteBuilding(): cd = buildGAPIObject('directory') buildingId = _getBuildingByNameOrId(cd, sys.argv[3]) print('Deleting building %s...' % buildingId) - callGAPI(cd.resources().buildings(), 'delete', + gapi.call(cd.resources().buildings(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], buildingId=buildingId) def _getFeatureAttributes(args, body={}): @@ -9330,14 +9072,14 @@ def _getFeatureAttributes(args, body={}): body['name'] = args[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam create|update feature"') + controlflow.system_error_exit(3, '%s is not a valid argument for "gam create|update feature"') return body def doCreateFeature(): cd = buildGAPIObject('directory') body = _getFeatureAttributes(sys.argv[3:]) print('Creating feature %s...' % body['name']) - callGAPI(cd.resources().features(), 'insert', + gapi.call(cd.resources().features(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doUpdateFeature(): @@ -9348,7 +9090,7 @@ def doUpdateFeature(): oldName = sys.argv[3] body = {'newName': sys.argv[5:]} print('Updating feature %s...' % oldName) - callGAPI(cd.resources().features(), 'rename', + gapi.call(cd.resources().features(), 'rename', customer=GC_Values[GC_CUSTOMER_ID], oldName=oldName, body=body) @@ -9356,7 +9098,7 @@ def doDeleteFeature(): cd = buildGAPIObject('directory') featureKey = sys.argv[3] print('Deleting feature %s...' % featureKey) - callGAPI(cd.resources().features(), 'delete', + gapi.call(cd.resources().features(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], featureKey=featureKey) def _getResourceCalendarAttributes(cd, args, body={}): @@ -9399,7 +9141,7 @@ def _getResourceCalendarAttributes(cd, args, body={}): body['userVisibleDescription'] = args[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam create|update resource"' % args[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam create|update resource"' % args[i]) return body def doCreateResourceCalendar(): @@ -9408,7 +9150,7 @@ def doCreateResourceCalendar(): 'resourceName': sys.argv[4]} body = _getResourceCalendarAttributes(cd, sys.argv[5:], body) print('Creating resource %s...' % body['resourceId']) - callGAPI(cd.resources().calendars(), 'insert', + gapi.call(cd.resources().calendars(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doUpdateResourceCalendar(): @@ -9417,7 +9159,7 @@ def doUpdateResourceCalendar(): body = _getResourceCalendarAttributes(cd, sys.argv[4:]) # Use patch since it seems to work better. # update requires name to be set. - callGAPI(cd.resources().calendars(), 'patch', + gapi.call(cd.resources().calendars(), 'patch', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId, body=body, fields='') print('updated resource %s' % resId) @@ -9431,7 +9173,7 @@ def doUpdateUser(users, i): for user in users: userKey = user if vfe: - user_primary = callGAPI(cd.users(), 'get', userKey=userKey, fields='primaryEmail,id') + user_primary = gapi.call(cd.users(), 'get', userKey=userKey, fields='primaryEmail,id') userKey = user_primary['id'] user_primary = user_primary['primaryEmail'] user_name, user_domain = splitEmailAddress(user_primary) @@ -9439,19 +9181,19 @@ def doUpdateUser(users, i): body['emails'] = [{'type': 'custom', 'customType': 'former_employee', 'primary': False, 'address': user_primary}] sys.stdout.write('updating user %s...\n' % user) if body: - callGAPI(cd.users(), 'update', userKey=userKey, body=body) + gapi.call(cd.users(), 'update', userKey=userKey, body=body) def doRemoveUsersAliases(users): cd = buildGAPIObject('directory') for user in users: - user_aliases = callGAPI(cd.users(), 'get', userKey=user, fields='aliases,id,primaryEmail') + user_aliases = gapi.call(cd.users(), 'get', userKey=user, fields='aliases,id,primaryEmail') user_id = user_aliases['id'] user_primary = user_aliases['primaryEmail'] if 'aliases' in user_aliases: print('%s has %s aliases' % (user_primary, len(user_aliases['aliases']))) for an_alias in user_aliases['aliases']: print(' removing alias %s for %s...' % (an_alias, user_primary)) - callGAPI(cd.users().aliases(), 'delete', userKey=user_id, alias=an_alias) + gapi.call(cd.users().aliases(), 'delete', userKey=user_id, alias=an_alias) else: print('%s has no aliases' % user_primary) @@ -9465,16 +9207,16 @@ def deleteUserFromGroups(users): for user_group in user_groups: j += 1 print(' removing %s from %s (%s/%s)' % (user, user_group['email'], j, num_groups)) - callGAPI(cd.members(), 'delete', soft_errors=True, groupKey=user_group['id'], memberKey=user) + gapi.call(cd.members(), 'delete', soft_errors=True, groupKey=user_group['id'], memberKey=user) print('') def checkGroupExists(cd, group, i=0, count=0): group = normalizeEmailAddressOrUID(group) try: - return callGAPI(cd.groups(), 'get', - throw_reasons=GAPI_GROUP_GET_THROW_REASONS, retry_reasons=GAPI_GROUP_GET_RETRY_REASONS, + return gapi.call(cd.groups(), 'get', + throw_reasons=gapi.errors.GROUP_GET_THROW_REASONS, retry_reasons=gapi.errors.GROUP_GET_RETRY_REASONS, groupKey=group, fields='email')['email'] - except (GAPI_groupNotFound, GAPI_domainNotFound, GAPI_domainCannotUseApis, GAPI_forbidden, GAPI_badRequest): + except (gapi.errors.GapiGroupNotFoundError, gapi.errors.GapiDomainNotFoundError, gapi.errors.GapiDomainCannotUseApisError, gapi.errors.GapiForbiddenError, gapi.errors.GapiBadRequestError): entityUnknownWarning('Group', group, i, count) return None @@ -9560,23 +9302,23 @@ def _getRoleAndUsers(): add_text.append('delivery %s' % delivery) for i in range(2): try: - callGAPI(cd.members(), 'insert', - throw_reasons=[GAPI_DUPLICATE, GAPI_MEMBER_NOT_FOUND, GAPI_RESOURCE_NOT_FOUND, GAPI_INVALID_MEMBER, GAPI_CYCLIC_MEMBERSHIPS_NOT_ALLOWED], + gapi.call(cd.members(), 'insert', + throw_reasons=[gapi.errors.ErrorReason.DUPLICATE, gapi.errors.ErrorReason.MEMBER_NOT_FOUND, gapi.errors.ErrorReason.RESOURCE_NOT_FOUND, gapi.errors.ErrorReason.INVALID_MEMBER, gapi.errors.ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED], groupKey=group, body=body) print(' Group: {0}, {1} Added {2}'.format(group, users_email[0], ' '.join(add_text))) break - except GAPI_duplicate as e: + except gapi.errors.GapiDuplicateError as e: # check if user is a full member, not pending try: - result = callGAPI(cd.members(), 'get', throw_reasons=[GAPI_MEMBER_NOT_FOUND], memberKey=users_email[0], groupKey=group, fields='role') + result = gapi.call(cd.members(), 'get', throw_reasons=[gapi.errors.ErrorReason.MEMBER_NOT_FOUND], memberKey=users_email[0], groupKey=group, fields='role') print(' Group: {0}, {1} Add {2} Failed: Duplicate, already a {3}'.format(group, users_email[0], ' '.join(add_text), result['role'])) break # if get succeeds, user is a full member and we throw duplicate error - except GAPI_memberNotFound: + except gapi.errors.GapiMemberNotFoundError: # insert fails on duplicate and get fails on not found, user is pending print(' Group: {0}, {1} member is pending, deleting and re-adding to solve...'.format(group, users_email[0])) - callGAPI(cd.members(), 'delete', memberKey=users_email[0], groupKey=group) + gapi.call(cd.members(), 'delete', memberKey=users_email[0], groupKey=group) continue # 2nd insert should succeed now that pending is clear - except (GAPI_memberNotFound, GAPI_resourceNotFound, GAPI_invalidMember, GAPI_cyclicMembershipsNotAllowed) as e: + except (gapi.errors.GapiMemberNotFoundError, gapi.errors.GapiResourceNotFoundError, gapi.errors.GapiInvalidMemberError, gapi.errors.GapiCyclicMembershipsNotAllowedError) as e: print(' Group: {0}, {1} Add {2} Failed: {3}'.format(group, users_email[0], ' '.join(add_text), str(e))) break elif myarg == 'sync': @@ -9621,11 +9363,11 @@ def _getRoleAndUsers(): items.append(['gam', 'update', 'group', group, 'remove', user_email]) else: try: - callGAPI(cd.members(), 'delete', - throw_reasons=[GAPI_MEMBER_NOT_FOUND, GAPI_INVALID_MEMBER], + gapi.call(cd.members(), 'delete', + throw_reasons=[gapi.errors.ErrorReason.MEMBER_NOT_FOUND, gapi.errors.ErrorReason.INVALID_MEMBER], groupKey=group, memberKey=users_email[0]) print(' Group: {0}, {1} Removed'.format(group, users_email[0])) - except (GAPI_memberNotFound, GAPI_invalidMember) as e: + except (gapi.errors.GapiMemberNotFoundError, gapi.errors.GapiInvalidMemberError) as e: print(' Group: {0}, {1} Remove Failed: {2}'.format(group, users_email[0], str(e))) elif myarg == 'update': role, users_email, delivery = _getRoleAndUsers() @@ -9653,11 +9395,11 @@ def _getRoleAndUsers(): body['delivery_settings'] = delivery update_text.append('delivery %s' % delivery) try: - callGAPI(cd.members(), 'update', - throw_reasons=[GAPI_MEMBER_NOT_FOUND, GAPI_INVALID_MEMBER], + gapi.call(cd.members(), 'update', + throw_reasons=[gapi.errors.ErrorReason.MEMBER_NOT_FOUND, gapi.errors.ErrorReason.INVALID_MEMBER], groupKey=group, memberKey=users_email[0], body=body) print(' Group: {0}, {1} Updated {2}'.format(group, users_email[0], ' '.join(update_text))) - except (GAPI_memberNotFound, GAPI_invalidMember) as e: + except (gapi.errors.GapiMemberNotFoundError, gapi.errors.GapiInvalidMemberError) as e: print(' Group: {0}, {1} Update to {2} Failed: {3}'.format(group, users_email[0], role, str(e))) else: # clear checkSuspended = None @@ -9674,7 +9416,7 @@ def _getRoleAndUsers(): fields.append('status') i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update group clear"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update group clear"' % sys.argv[i]) if roles: roles = ','.join(sorted(set(roles))) else: @@ -9687,7 +9429,7 @@ def _getRoleAndUsers(): try: result = callGAPIpages(cd.members(), 'list', 'members', page_message=page_message, - throw_reasons=GAPI_MEMBERS_THROW_REASONS, + throw_reasons=gapi.errors.MEMBERS_THROW_REASONS, groupKey=group, roles=listRoles, fields=listFields) if not result: print('Group already has 0 members') @@ -9699,13 +9441,13 @@ def _getRoleAndUsers(): items.append(['gam', 'update', 'group', group, 'remove', user_email]) else: try: - callGAPI(cd.members(), 'delete', - throw_reasons=[GAPI_MEMBER_NOT_FOUND, GAPI_INVALID_MEMBER], + gapi.call(cd.members(), 'delete', + throw_reasons=[gapi.errors.ErrorReason.MEMBER_NOT_FOUND, gapi.errors.ErrorReason.INVALID_MEMBER], groupKey=group, memberKey=users_email[0]) print(' Group: {0}, {1} Removed'.format(group, users_email[0])) - except (GAPI_memberNotFound, GAPI_invalidMember) as e: + except (gapi.errors.GapiMemberNotFoundError, gapi.errors.GapiInvalidMemberError) as e: print(' Group: {0}, {1} Remove Failed: {2}'.format(group, users_email[0], str(e))) - except (GAPI_groupNotFound, GAPI_domainNotFound, GAPI_invalid, GAPI_forbidden): + except (gapi.errors.GapiGroupNotFoundError, gapi.errors.GapiDomainNotFoundError, gapi.errors.GapiInvalidError, gapi.errors.GapiForbiddenError): entityUnknownWarning('Group', group, 0, 0) if items: run_batch(items) @@ -9736,17 +9478,17 @@ def _getRoleAndUsers(): i += 2 group = normalizeEmailAddressOrUID(group) if use_cd_api or (group.find('@') == -1): # group settings API won't take uid so we make sure cd API is used so that we can grab real email. - group = callGAPI(cd.groups(), 'update', groupKey=group, body=cd_body, fields='email')['email'] + group = gapi.call(cd.groups(), 'update', groupKey=group, body=cd_body, fields='email')['email'] if gs: if not GroupIsAbuseOrPostmaster(group): if gs_get_before_update: - current_settings = callGAPI(gs.groups(), 'get', + current_settings = gapi.call(gs.groups(), 'get', retry_reasons=['serviceLimit'], groupUniqueId=group, fields='*') if current_settings is not None: gs_body = dict(list(current_settings.items()) + list(gs_body.items())) if gs_body: - callGAPI(gs.groups(), 'update', retry_reasons=['serviceLimit'], groupUniqueId=group, body=gs_body) + gapi.call(gs.groups(), 'update', retry_reasons=['serviceLimit'], groupUniqueId=group, body=gs_body) print('updated group %s' % group) def doUpdateAlias(): @@ -9754,21 +9496,21 @@ def doUpdateAlias(): alias = normalizeEmailAddressOrUID(sys.argv[3], noUid=True, noLower=True) target_type = sys.argv[4].lower() if target_type not in ['user', 'group', 'target']: - systemErrorExit(2, 'target type must be one of user, group, target; got %s' % target_type) + controlflow.system_error_exit(2, 'target type must be one of user, group, target; got %s' % target_type) target_email = normalizeEmailAddressOrUID(sys.argv[5]) try: - callGAPI(cd.users().aliases(), 'delete', throw_reasons=[GAPI_INVALID], userKey=alias, alias=alias) - except GAPI_invalid: - callGAPI(cd.groups().aliases(), 'delete', groupKey=alias, alias=alias) + gapi.call(cd.users().aliases(), 'delete', throw_reasons=[gapi.errors.ErrorReason.INVALID], userKey=alias, alias=alias) + except gapi.errors.GapiInvalidError: + gapi.call(cd.groups().aliases(), 'delete', groupKey=alias, alias=alias) if target_type == 'user': - callGAPI(cd.users().aliases(), 'insert', userKey=target_email, body={'alias': alias}) + gapi.call(cd.users().aliases(), 'insert', userKey=target_email, body={'alias': alias}) elif target_type == 'group': - callGAPI(cd.groups().aliases(), 'insert', groupKey=target_email, body={'alias': alias}) + gapi.call(cd.groups().aliases(), 'insert', groupKey=target_email, body={'alias': alias}) elif target_type == 'target': try: - callGAPI(cd.users().aliases(), 'insert', throw_reasons=[GAPI_INVALID], userKey=target_email, body={'alias': alias}) - except GAPI_invalid: - callGAPI(cd.groups().aliases(), 'insert', groupKey=target_email, body={'alias': alias}) + gapi.call(cd.users().aliases(), 'insert', throw_reasons=[gapi.errors.ErrorReason.INVALID], userKey=target_email, body={'alias': alias}) + except gapi.errors.GapiInvalidError: + gapi.call(cd.groups().aliases(), 'insert', groupKey=target_email, body={'alias': alias}) print('updated alias %s' % alias) def getCrOSDeviceEntity(i, cd): @@ -9826,7 +9568,7 @@ def doUpdateCros(): action = 'deprovision' deprovisionReason = 'retiring_device' elif action not in ['disable', 'reenable']: - systemErrorExit(2, 'expected action of deprovision_same_model_replace, deprovision_different_model_replace, deprovision_retiring_device, disable or reenable, got %s' % action) + controlflow.system_error_exit(2, 'expected action of deprovision_same_model_replace, deprovision_different_model_replace, deprovision_retiring_device, disable or reenable, got %s' % action) action_body = {'action': action} if deprovisionReason: action_body['deprovisionReason'] = deprovisionReason @@ -9835,7 +9577,7 @@ def doUpdateCros(): ack_wipe = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update cros"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update cros"' % sys.argv[i]) i = 0 count = len(devices) if action_body: @@ -9845,20 +9587,20 @@ def doUpdateCros(): for deviceId in devices: i += 1 print(' performing action %s for %s (%s of %s)' % (action, deviceId, i, count)) - callGAPI(cd.chromeosdevices(), function='action', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=deviceId, body=action_body) + 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 print(' updating %s (%s of %s)' % (deviceId, i, count)) - callGAPI(service=cd.chromeosdevices(), function='update', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, body=update_body) + gapi.call(service=cd.chromeosdevices(), function='update', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, body=update_body) if orgUnitPath: #move_body[u'deviceIds'] = devices # split moves into max 50 devices per batch for l in range(0, len(devices), 50): move_body = {'deviceIds': devices[l:l+50]} print(' moving %s devices to %s' % (len(move_body['deviceIds']), orgUnitPath)) - callGAPI(cd.chromeosdevices(), 'moveDevicesToOu', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=move_body) + gapi.call(cd.chromeosdevices(), 'moveDevicesToOu', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=move_body) def doUpdateMobile(): cd = buildGAPIObject('directory') @@ -9887,7 +9629,7 @@ def doUpdateMobile(): elif body['action'].replace('_', '') in ['accountwipe', 'wipeaccount']: body['action'] = 'admin_account_wipe' if body['action'] not in ['admin_remote_wipe', 'admin_account_wipe', 'approve', 'block', 'cancel_remote_wipe_then_activate', 'cancel_remote_wipe_then_block']: - systemErrorExit(2, 'action must be one of wipe, wipeaccount, approve, block, cancel_remote_wipe_then_activate, cancel_remote_wipe_then_block; got %s' % body['action']) + controlflow.system_error_exit(2, 'action must be one of wipe, wipeaccount, approve, block, cancel_remote_wipe_then_activate, cancel_remote_wipe_then_block; got %s' % body['action']) i += 2 elif myarg in ['ifusers', 'matchusers']: match_users = getUsersToModify(entity_type=sys.argv[i+1].lower(), entity=sys.argv[i+2]) @@ -9896,7 +9638,7 @@ def doUpdateMobile(): doit = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update mobile"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update mobile"' % sys.argv[i]) if body: if doit: print('Updating %s devices' % len(devices)) @@ -9911,12 +9653,12 @@ def doUpdateMobile(): else: print('%s %s on user %s device %s' % (describe_as, body['action'], device_user, device['resourceId'])) if doit: - callGAPI(cd.mobiledevices(), 'action', resourceId=device['resourceId'], body=body, customerId=GC_Values[GC_CUSTOMER_ID]) + gapi.call(cd.mobiledevices(), 'action', resourceId=device['resourceId'], body=body, customerId=GC_Values[GC_CUSTOMER_ID]) def doDeleteMobile(): cd = buildGAPIObject('directory') resourceId = sys.argv[3] - callGAPI(cd.mobiledevices(), 'delete', resourceId=resourceId, customerId=GC_Values[GC_CUSTOMER_ID]) + gapi.call(cd.mobiledevices(), 'delete', resourceId=resourceId, customerId=GC_Values[GC_CUSTOMER_ID]) def doUpdateOrg(): cd = buildGAPIObject('directory') @@ -9932,7 +9674,7 @@ def doUpdateOrg(): for l in range(0, len(users), 50): move_body = {'deviceIds': users[l:l+50]} print(' moving %s devices to %s' % (len(move_body['deviceIds']), orgUnitPath)) - callGAPI(cd.chromeosdevices(), 'moveDevicesToOu', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=move_body) + gapi.call(cd.chromeosdevices(), 'moveDevicesToOu', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=move_body) else: current_user = 0 user_count = len(users) @@ -9940,8 +9682,8 @@ def doUpdateOrg(): current_user += 1 sys.stderr.write(' moving %s to %s (%s/%s)\n' % (user, orgUnitPath, current_user, user_count)) try: - callGAPI(cd.users(), 'update', throw_reasons=[GAPI_CONDITION_NOT_MET], userKey=user, body={'orgUnitPath': orgUnitPath}) - except GAPI_conditionNotMet: + gapi.call(cd.users(), 'update', throw_reasons=[gapi.errors.ErrorReason.CONDITION_NOT_MET], userKey=user, body={'orgUnitPath': orgUnitPath}) + except gapi.errors.GapiConditionNotMetError: pass else: body = {} @@ -9968,14 +9710,14 @@ def doUpdateOrg(): body['blockInheritance'] = False i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam update org"' % sys.argv[i]) - callGAPI(cd.orgunits(), 'update', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(orgUnitPath)), body=body) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update org"' % sys.argv[i]) + gapi.call(cd.orgunits(), 'update', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(orgUnitPath)), body=body) def doWhatIs(): cd = buildGAPIObject('directory') email = normalizeEmailAddressOrUID(sys.argv[2]) try: - user_or_alias = callGAPI(cd.users(), 'get', throw_reasons=[GAPI_NOT_FOUND, GAPI_BAD_REQUEST, GAPI_INVALID], userKey=email, fields='id,primaryEmail') + user_or_alias = gapi.call(cd.users(), 'get', throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.BAD_REQUEST, gapi.errors.ErrorReason.INVALID], userKey=email, fields='id,primaryEmail') if (user_or_alias['primaryEmail'].lower() == email) or (user_or_alias['id'] == email): sys.stderr.write('%s is a user\n\n' % email) doGetUserInfo(user_email=email) @@ -9983,13 +9725,13 @@ def doWhatIs(): sys.stderr.write('%s is a user alias\n\n' % email) doGetAliasInfo(alias_email=email) return - except (GAPI_notFound, GAPI_badRequest, GAPI_invalid): + except (gapi.errors.GapiNotFoundError, gapi.errors.GapiBadRequestError, gapi.errors.GapiInvalidError): sys.stderr.write('%s is not a user...\n' % email) sys.stderr.write('%s is not a user alias...\n' % email) try: - group = callGAPI(cd.groups(), 'get', throw_reasons=[GAPI_NOT_FOUND, GAPI_BAD_REQUEST], groupKey=email, fields='id,email') - except (GAPI_notFound, GAPI_badRequest): - systemErrorExit(1, '%s is not a group either!\n\nDoesn\'t seem to exist!\n\n' % email) + group = gapi.call(cd.groups(), 'get', throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.BAD_REQUEST], groupKey=email, fields='id,email') + except (gapi.errors.GapiNotFoundError, gapi.errors.GapiBadRequestError): + controlflow.system_error_exit(1, '%s is not a group either!\n\nDoesn\'t seem to exist!\n\n' % email) if (group['email'].lower() == email) or (group['id'] == email): sys.stderr.write('%s is a group\n\n' % email) doGetGroupInfo(group_name=email) @@ -9998,11 +9740,11 @@ def doWhatIs(): doGetAliasInfo(alias_email=email) def convertSKU2ProductId(res, sku, customerId): - results = callGAPI(res.subscriptions(), 'list', customerId=customerId) + results = gapi.call(res.subscriptions(), 'list', customerId=customerId) for subscription in results['subscriptions']: if sku == subscription['skuId']: return subscription['subscriptionId'] - systemErrorExit(3, 'could not find subscription for customer %s and SKU %s' % (customerId, sku)) + controlflow.system_error_exit(3, 'could not find subscription for customer %s and SKU %s' % (customerId, sku)) def doDeleteResoldSubscription(): res = buildGAPIObject('reseller') @@ -10010,14 +9752,14 @@ def doDeleteResoldSubscription(): sku = sys.argv[4] deletionType = sys.argv[5] subscriptionId = convertSKU2ProductId(res, sku, customerId) - callGAPI(res.subscriptions(), 'delete', customerId=customerId, subscriptionId=subscriptionId, deletionType=deletionType) + gapi.call(res.subscriptions(), 'delete', customerId=customerId, subscriptionId=subscriptionId, deletionType=deletionType) print('Cancelled %s for %s' % (sku, customerId)) def doCreateResoldSubscription(): res = buildGAPIObject('reseller') customerId = sys.argv[3] customerAuthToken, body = _getResoldSubscriptionAttr(sys.argv[4:], customerId) - result = callGAPI(res.subscriptions(), 'insert', customerId=customerId, customerAuthToken=customerAuthToken, body=body, fields='customerId') + result = gapi.call(res.subscriptions(), 'insert', customerId=customerId, customerAuthToken=customerAuthToken, body=body, fields='customerId') print('Created subscription:') print_json(None, result) @@ -10072,10 +9814,10 @@ def doUpdateResoldSubscription(): kwargs['body']['dealCode'] = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam update resoldsubscription plan"' % planarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam update resoldsubscription plan"' % planarg) else: - systemErrorExit(3, '%s is not a valid argument to "gam update resoldsubscription"' % myarg) - result = callGAPI(res.subscriptions(), function, customerId=customerId, subscriptionId=subscriptionId, **kwargs) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam update resoldsubscription"' % myarg) + result = gapi.call(res.subscriptions(), function, customerId=customerId, subscriptionId=subscriptionId, **kwargs) print('Updated %s SKU %s subscription:' % (customerId, sku)) if result: print_json(None, result) @@ -10091,8 +9833,8 @@ def doGetResoldSubscriptions(): customerAuthToken = sys.argv[i+1] i += 2 else: - systemErrorExit(3, '%s is not a valid argument for "gam info resoldsubscriptions"' % myarg) - result = callGAPI(res.subscriptions(), 'list', customerId=customerId, customerAuthToken=customerAuthToken) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam info resoldsubscriptions"' % myarg) + result = gapi.call(res.subscriptions(), 'list', customerId=customerId, customerAuthToken=customerAuthToken) print_json(None, result) def _getResoldSubscriptionAttr(arg, customerId): @@ -10119,14 +9861,14 @@ def _getResoldSubscriptionAttr(arg, customerId): elif myarg in ['customerauthtoken', 'transfertoken']: customerAuthToken = arg[i+1] else: - systemErrorExit(3, '%s is not a valid argument for "gam create resoldsubscription"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam create resoldsubscription"' % myarg) i += 2 return customerAuthToken, body def doGetResoldCustomer(): res = buildGAPIObject('reseller') customerId = sys.argv[3] - result = callGAPI(res.customers(), 'get', customerId=customerId) + result = gapi.call(res.customers(), 'get', customerId=customerId) print_json(None, result) def _getResoldCustomerAttr(arg): @@ -10145,7 +9887,7 @@ def _getResoldCustomerAttr(arg): elif myarg in ['customerauthtoken', 'transfertoken']: customerAuthToken = arg[i+1] else: - systemErrorExit(3, '%s is not a valid argument for "gam %s resoldcustomer"' % (myarg, sys.argv[1])) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam %s resoldcustomer"' % (myarg, sys.argv[1])) i += 2 return customerAuthToken, body @@ -10153,20 +9895,20 @@ def doUpdateResoldCustomer(): res = buildGAPIObject('reseller') customerId = sys.argv[3] customerAuthToken, body = _getResoldCustomerAttr(sys.argv[4:]) - callGAPI(res.customers(), 'patch', customerId=customerId, body=body, customerAuthToken=customerAuthToken, fields='customerId') + gapi.call(res.customers(), 'patch', customerId=customerId, body=body, customerAuthToken=customerAuthToken, fields='customerId') print('updated customer %s' % customerId) def doCreateResoldCustomer(): res = buildGAPIObject('reseller') customerAuthToken, body = _getResoldCustomerAttr(sys.argv[4:]) body['customerDomain'] = sys.argv[3] - result = callGAPI(res.customers(), 'insert', body=body, customerAuthToken=customerAuthToken, fields='customerId,customerDomain') + result = gapi.call(res.customers(), 'insert', body=body, customerAuthToken=customerAuthToken, fields='customerId,customerDomain') print('Created customer %s with id %s' % (result['customerDomain'], result['customerId'])) def _getValueFromOAuth(field, credentials=None): if not GC_Values[GC_DECODED_ID_TOKEN]: credentials = credentials if credentials is not None else getValidOauth2TxtCredentials() - http = google_auth_httplib2.Request(_createHttpObj()) + http = google_auth_httplib2.Request(gapi.create_http()) GC_Values[GC_DECODED_ID_TOKEN] = google.oauth2.id_token.verify_oauth2_token(credentials.id_token, http) return GC_Values[GC_DECODED_ID_TOKEN].get(field, 'Unknown') @@ -10174,7 +9916,7 @@ def doGetMemberInfo(): cd = buildGAPIObject('directory') memberKey = normalizeEmailAddressOrUID(sys.argv[3]) groupKey = normalizeEmailAddressOrUID(sys.argv[4]) - info = callGAPI(cd.members(), 'get', memberKey=memberKey, groupKey=groupKey) + info = gapi.call(cd.members(), 'get', memberKey=memberKey, groupKey=groupKey) print_json(None, info) def doGetUserInfo(user_email=None): @@ -10225,8 +9967,8 @@ def user_lic_result(request_id, response, exception): elif myarg in ['nousers', 'groups']: i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam info user"' % myarg) - user = callGAPI(cd.users(), 'get', userKey=user_email, projection=projection, customFieldMask=customFieldMask, viewType=viewType) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info user"' % myarg) + user = gapi.call(cd.users(), 'get', userKey=user_email, projection=projection, customFieldMask=customFieldMask, viewType=viewType) print('User: %s' % user['primaryEmail']) if 'name' in user and 'givenName' in user['name']: print(utils.convertUTF8('First Name: %s' % user['name']['givenName'])) @@ -10487,16 +10229,16 @@ def doGetGroupInfo(group_name=None): if myarg == 'schemas': i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam info group"' % myarg) - basic_info = callGAPI(cd.groups(), 'get', groupKey=group_name) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info group"' % myarg) + basic_info = gapi.call(cd.groups(), 'get', groupKey=group_name) settings = {} if not GroupIsAbuseOrPostmaster(basic_info['email']): try: - settings = callGAPI(gs.groups(), 'get', throw_reasons=[GAPI_AUTH_ERROR], retry_reasons=['serviceLimit'], + settings = gapi.call(gs.groups(), 'get', throw_reasons=[gapi.errors.ErrorReason.AUTH_ERROR], retry_reasons=['serviceLimit'], groupUniqueId=basic_info['email']) # Use email address retrieved from cd since GS API doesn't support uid if settings is None: settings = {} - except GAPI_authError: + except gapi.errors.GapiAuthErrorError: pass print('') print('Group Settings:') @@ -10532,13 +10274,13 @@ def doGetAliasInfo(alias_email=None): if alias_email is None: alias_email = normalizeEmailAddressOrUID(sys.argv[3]) try: - result = callGAPI(cd.users(), 'get', throw_reasons=[GAPI_INVALID, GAPI_BAD_REQUEST], userKey=alias_email) - except (GAPI_invalid, GAPI_badRequest): - result = callGAPI(cd.groups(), 'get', groupKey=alias_email) + result = gapi.call(cd.users(), 'get', throw_reasons=[gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST], userKey=alias_email) + except (gapi.errors.GapiInvalidError, gapi.errors.GapiBadRequestError): + result = gapi.call(cd.groups(), 'get', groupKey=alias_email) print(' Alias Email: %s' % alias_email) try: if result['primaryEmail'].lower() == alias_email.lower(): - systemErrorExit(3, '%s is a primary user email address, not an alias.' % alias_email) + controlflow.system_error_exit(3, '%s is a primary user email address, not an alias.' % alias_email) print(' User Email: %s' % result['primaryEmail']) except KeyError: print(' Group Email: %s' % result['email']) @@ -10547,7 +10289,7 @@ def doGetAliasInfo(alias_email=None): def doGetResourceCalendarInfo(): cd = buildGAPIObject('directory') resId = sys.argv[3] - resource = callGAPI(cd.resources().calendars(), 'get', + resource = gapi.call(cd.resources().calendars(), 'get', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId) if 'featureInstances' in resource: resource['features'] = ', '.join([a_feature['feature']['name'] for a_feature in resource.pop('featureInstances')]) @@ -10630,7 +10372,7 @@ def doGetCrosInfo(): projection = 'FULL' noLists = False else: - systemErrorExit(2, '%s is not a valid argument for "gam info cros fields"' % field) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info cros fields"' % field) i += 2 elif myarg == 'downloadfile': downloadfile = sys.argv[i+1] @@ -10643,7 +10385,7 @@ def doGetCrosInfo(): os.makedirs(targetFolder) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam info cros"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info cros"' % sys.argv[i]) if fieldsList: fieldsList.append('deviceId') if guess_aue: @@ -10655,7 +10397,7 @@ def doGetCrosInfo(): device_count = len(devices) for deviceId in devices: i += 1 - cros = callGAPI(cd.chromeosdevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], + cros = gapi.call(cd.chromeosdevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, projection=projection, fields=fields) print('CrOS Device: {0} ({1} of {2})'.format(deviceId, i, device_count)) if 'notes' in cros: @@ -10747,7 +10489,7 @@ def doGetCrosInfo(): def doGetMobileInfo(): cd = buildGAPIObject('directory') resourceId = sys.argv[3] - info = callGAPI(cd.mobiledevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=resourceId) + info = gapi.call(cd.mobiledevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=resourceId) if 'deviceId' in info: info['deviceId'] = info['deviceId'].encode('unicode-escape').decode(UTF8) attrib = 'securityPatchLevel' @@ -10783,11 +10525,11 @@ def print_json(object_name, object_value, spacing=''): def doSiteVerifyShow(): verif = buildGAPIObject('siteVerification') a_domain = sys.argv[3] - txt_record = callGAPI(verif.webResource(), 'getToken', body={'site':{'type':'INET_DOMAIN', 'identifier':a_domain}, 'verificationMethod':'DNS_TXT'}) + txt_record = gapi.call(verif.webResource(), 'getToken', body={'site':{'type':'INET_DOMAIN', 'identifier':a_domain}, 'verificationMethod':'DNS_TXT'}) print('TXT Record Name: %s' % a_domain) print('TXT Record Value: %s' % txt_record['token']) print() - cname_record = callGAPI(verif.webResource(), 'getToken', body={'site':{'type':'INET_DOMAIN', 'identifier':a_domain}, 'verificationMethod':'DNS_CNAME'}) + cname_record = gapi.call(verif.webResource(), 'getToken', body={'site':{'type':'INET_DOMAIN', 'identifier':a_domain}, 'verificationMethod':'DNS_CNAME'}) cname_token = cname_record['token'] cname_list = cname_token.split(' ') cname_subdomain = cname_list[0] @@ -10795,13 +10537,13 @@ def doSiteVerifyShow(): print('CNAME Record Name: %s.%s' % (cname_subdomain, a_domain)) print('CNAME Record Value: %s' % cname_value) print('') - webserver_file_record = callGAPI(verif.webResource(), 'getToken', body={'site':{'type':'SITE', 'identifier':'http://%s/' % a_domain}, 'verificationMethod':'FILE'}) + webserver_file_record = gapi.call(verif.webResource(), 'getToken', body={'site':{'type':'SITE', 'identifier':'http://%s/' % a_domain}, 'verificationMethod':'FILE'}) webserver_file_token = webserver_file_record['token'] print('Saving web server verification file to: %s' % webserver_file_token) writeFile(webserver_file_token, 'google-site-verification: {0}'.format(webserver_file_token), continueOnError=True) print('Verification File URL: http://%s/%s' % (a_domain, webserver_file_token)) print() - webserver_meta_record = callGAPI(verif.webResource(), 'getToken', body={'site':{'type':'SITE', 'identifier':'http://%s/' % a_domain}, 'verificationMethod':'META'}) + webserver_meta_record = gapi.call(verif.webResource(), 'getToken', body={'site':{'type':'SITE', 'identifier':'http://%s/' % a_domain}, 'verificationMethod':'META'}) print('Meta URL: http://%s/' % a_domain) print('Meta HTML Header Data: %s' % webserver_meta_record['token']) print() @@ -10836,14 +10578,14 @@ def doSiteVerifyAttempt(): identifier = 'http://%s/' % a_domain body = {'site':{'type':verify_type, 'identifier':identifier}, 'verificationMethod':verificationMethod} try: - verify_result = callGAPI(verif.webResource(), 'insert', throw_reasons=[GAPI_BAD_REQUEST], verificationMethod=verificationMethod, body=body) - except GAPI_badRequest as e: + verify_result = gapi.call(verif.webResource(), 'insert', throw_reasons=[gapi.errors.ErrorReason.BAD_REQUEST], verificationMethod=verificationMethod, body=body) + except gapi.errors.GapiBadRequestError as e: print('ERROR: %s' % str(e)) - verify_data = callGAPI(verif.webResource(), 'getToken', body=body) + verify_data = gapi.call(verif.webResource(), 'getToken', body=body) print('Method: %s' % verify_data['method']) print('Expected Token: %s' % verify_data['token']) if verify_data['method'] in ['DNS_CNAME', 'DNS_TXT']: - simplehttp = _createHttpObj() + simplehttp = gapi.create_http() base_url = 'https://dns.google/resolve?' query_params = {} if verify_data['method'] == 'DNS_CNAME': @@ -10874,9 +10616,9 @@ def doSiteVerifyAttempt(): print('Unrelated TXT record: %s' % possible_answer['data']) print('Found DNS Record: %s' % answer) elif status == 0: - systemErrorExit(1, 'DNS record not found') + controlflow.system_error_exit(1, 'DNS record not found') else: - systemErrorExit(status, DNS_ERROR_CODES_MAP.get(status, 'Unknown error %s' % status)) + controlflow.system_error_exit(status, DNS_ERROR_CODES_MAP.get(status, 'Unknown error %s' % status)) return print('SUCCESS!') print('Verified: %s' % verify_result['site']['identifier']) @@ -10934,7 +10676,7 @@ def encodeOrgUnitPath(path): def getOrgUnitItem(orgUnit, pathOnly=False, absolutePath=True): if pathOnly and (orgUnit.startswith('id:') or orgUnit.startswith('uid:')): - systemErrorExit(2, '%s is not valid in this context' % orgUnit) + controlflow.system_error_exit(2, '%s is not valid in this context' % orgUnit) if absolutePath: return makeOrgUnitPathAbsolute(orgUnit) return makeOrgUnitPathRelative(orgUnit) @@ -10945,17 +10687,17 @@ def getOrgUnitId(orgUnit, cd=None): orgUnit = getOrgUnitItem(orgUnit) if orgUnit[:3] == 'id:': return (orgUnit, orgUnit) - result = callGAPI(cd.orgunits(), 'get', + result = gapi.call(cd.orgunits(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(orgUnit)), fields='orgUnitId') return (orgUnit, result['orgUnitId']) def getTopLevelOrgId(cd, orgUnitPath): try: # create a temp org so we can learn what the top level org ID is (sigh) - temp_org = callGAPI(cd.orgunits(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], + temp_org = gapi.call(cd.orgunits(), 'insert', customerId=GC_Values[GC_CUSTOMER_ID], body={'name': 'temp-delete-me', 'parentOrgUnitPath': orgUnitPath}, fields='parentOrgUnitId,orgUnitId') - callGAPI(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=temp_org['orgUnitId']) + gapi.call(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=temp_org['orgUnitId']) return temp_org['parentOrgUnitId'] except: pass @@ -10981,9 +10723,9 @@ def doGetOrgInfo(name=None, return_attrib=None): checkSuspended = myarg == 'suspended' i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam info org"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info org"' % sys.argv[i]) if name == '/': - orgs = callGAPI(cd.orgunits(), 'list', + orgs = gapi.call(cd.orgunits(), 'list', customerId=GC_Values[GC_CUSTOMER_ID], type='children', fields='organizationUnits/parentOrgUnitId') if 'organizationUnits' in orgs and orgs['organizationUnits']: @@ -10994,7 +10736,7 @@ def doGetOrgInfo(name=None, return_attrib=None): name = topLevelOrgId else: name = makeOrgUnitPathRelative(name) - result = callGAPI(cd.orgunits(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(name)) + result = gapi.call(cd.orgunits(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(name)) if return_attrib: return result[return_attrib] print_json(None, result) @@ -11050,7 +10792,7 @@ def doDelASP(users): asps = callGAPIitems(cd.asps(), 'list', 'items', userKey=user, fields='items/codeId') codeIds = [asp['codeId'] for asp in asps] for codeId in codeIds: - callGAPI(cd.asps(), 'delete', userKey=user, codeId=codeId) + gapi.call(cd.asps(), 'delete', userKey=user, codeId=codeId) print('deleted ASP %s for %s' % (codeId, user)) def printBackupCodes(user, codes): @@ -11072,15 +10814,15 @@ def doGetBackupCodes(users): cd = buildGAPIObject('directory') for user in users: try: - codes = callGAPIitems(cd.verificationCodes(), 'list', 'items', throw_reasons=[GAPI_INVALID_ARGUMENT, GAPI_INVALID], userKey=user) - except (GAPI_invalidArgument, GAPI_invalid): + codes = callGAPIitems(cd.verificationCodes(), 'list', 'items', throw_reasons=[gapi.errors.ErrorReason.INVALID_ARGUMENT, gapi.errors.ErrorReason.INVALID], userKey=user) + except (gapi.errors.GapiInvalidArgumentError, gapi.errors.GapiInvalidError): codes = [] printBackupCodes(user, codes) def doGenBackupCodes(users): cd = buildGAPIObject('directory') for user in users: - callGAPI(cd.verificationCodes(), 'generate', userKey=user) + gapi.call(cd.verificationCodes(), 'generate', userKey=user) codes = callGAPIitems(cd.verificationCodes(), 'list', 'items', userKey=user) printBackupCodes(user, codes) @@ -11088,8 +10830,8 @@ def doDelBackupCodes(users): cd = buildGAPIObject('directory') for user in users: try: - callGAPI(cd.verificationCodes(), 'invalidate', soft_errors=True, throw_reasons=[GAPI_INVALID], userKey=user) - except GAPI_invalid: + gapi.call(cd.verificationCodes(), 'invalidate', soft_errors=True, throw_reasons=[gapi.errors.ErrorReason.INVALID], userKey=user) + except gapi.errors.GapiInvalidError: print('No 2SV backup codes for %s' % user) continue print('2SV backup codes for %s invalidated' % user) @@ -11109,16 +10851,16 @@ def doDelTokens(users): clientId = commonClientIds(sys.argv[i+1]) i += 2 else: - systemErrorExit(3, '%s is not a valid argument to "gam delete token"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam delete token"' % sys.argv[i]) if not clientId: - systemErrorExit(3, 'you must specify a clientid for "gam delete token"') + controlflow.system_error_exit(3, 'you must specify a clientid for "gam delete token"') for user in users: try: - callGAPI(cd.tokens(), 'get', throw_reasons=[GAPI_NOT_FOUND, GAPI_RESOURCE_NOT_FOUND], userKey=user, clientId=clientId) - except (GAPI_notFound, GAPI_resourceNotFound): + gapi.call(cd.tokens(), 'get', throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.RESOURCE_NOT_FOUND], userKey=user, clientId=clientId) + except (gapi.errors.GapiNotFoundError, gapi.errors.GapiResourceNotFoundError): print('User %s did not authorize %s' % (user, clientId)) continue - callGAPI(cd.tokens(), 'delete', userKey=user, clientId=clientId) + gapi.call(cd.tokens(), 'delete', userKey=user, clientId=clientId) print('Deleted token for %s' % user) def printShowTokens(i, entityType, users, csvFormat): @@ -11151,7 +10893,7 @@ def _showToken(token): users = getUsersToModify(entity_type=entityType, entity=sys.argv[i+1], silent=False) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam %s tokens"' % (myarg, ['show', 'print'][csvFormat])) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s tokens"' % (myarg, ['show', 'print'][csvFormat])) if not entityType: users = getUsersToModify(entity_type='all', entity='users', silent=False) fields = ','.join(['clientId', 'displayText', 'anonymous', 'nativeApp', 'userKey', 'scopes']) @@ -11163,12 +10905,12 @@ def _showToken(token): if csvFormat: sys.stderr.write('Getting Access Tokens for %s\n' % (user)) if clientId: - results = [callGAPI(cd.tokens(), 'get', - throw_reasons=[GAPI_NOT_FOUND, GAPI_USER_NOT_FOUND, GAPI_RESOURCE_NOT_FOUND], + results = [gapi.call(cd.tokens(), 'get', + throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.USER_NOT_FOUND, gapi.errors.ErrorReason.RESOURCE_NOT_FOUND], userKey=user, clientId=clientId, fields=fields)] else: results = callGAPIitems(cd.tokens(), 'list', 'items', - throw_reasons=[GAPI_USER_NOT_FOUND], + throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND], userKey=user, fields='items({0})'.format(fields)) jcount = len(results) if not csvFormat: @@ -11186,7 +10928,7 @@ def _showToken(token): if item not in ['scopes']: row[item] = token.get(item, '') csvRows.append(row) - except (GAPI_notFound, GAPI_userNotFound, GAPI_resourceNotFound): + except (gapi.errors.GapiNotFoundError, gapi.errors.GapiUserNotFoundError, gapi.errors.GapiResourceNotFoundError): pass if csvFormat: writeCSVfile(csvRows, titles, 'OAuth Tokens', todrive) @@ -11202,13 +10944,13 @@ def doDeprovUser(users): for asp in asps: j += 1 print(' deleting ASP %s of %s' % (j, jcount)) - callGAPI(cd.asps(), 'delete', userKey=user, codeId=asp['codeId']) + gapi.call(cd.asps(), 'delete', userKey=user, codeId=asp['codeId']) else: print('No ASPs') print('Invalidating 2SV Backup Codes for %s' % user) try: - callGAPI(cd.verificationCodes(), 'invalidate', soft_errors=True, throw_reasons=[GAPI_INVALID], userKey=user) - except GAPI_invalid: + gapi.call(cd.verificationCodes(), 'invalidate', soft_errors=True, throw_reasons=[gapi.errors.ErrorReason.INVALID], userKey=user) + except gapi.errors.GapiInvalidError: print('No 2SV Backup Codes') print('Getting tokens for %s...' % user) tokens = callGAPIitems(cd.tokens(), 'list', 'items', userKey=user, fields='items/clientId') @@ -11218,7 +10960,7 @@ def doDeprovUser(users): for token in tokens: j += 1 print(' deleting token %s of %s' % (j, jcount)) - callGAPI(cd.tokens(), 'delete', userKey=user, clientId=token['clientId']) + gapi.call(cd.tokens(), 'delete', userKey=user, clientId=token['clientId']) else: print('No Tokens') print('Done deprovisioning %s' % user) @@ -11227,7 +10969,7 @@ def doDeleteUser(): cd = buildGAPIObject('directory') user_email = normalizeEmailAddressOrUID(sys.argv[3]) print("Deleting account for %s" % (user_email)) - callGAPI(cd.users(), 'delete', userKey=user_email) + gapi.call(cd.users(), 'delete', userKey=user_email) def doUndeleteUser(): cd = buildGAPIObject('directory') @@ -11240,7 +10982,7 @@ def doUndeleteUser(): orgUnit = makeOrgUnitPathAbsolute(sys.argv[i+1]) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam undelete user"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam undelete user"' % sys.argv[i]) if user.find('@') == -1: user_uid = user else: @@ -11252,7 +10994,7 @@ def doUndeleteUser(): if str(deleted_user['primaryEmail']).lower() == user: matching_users.append(deleted_user) if not matching_users: - systemErrorExit(3, 'could not find deleted user with that address.') + controlflow.system_error_exit(3, 'could not find deleted user with that address.') elif len(matching_users) > 1: print('ERROR: more than one matching deleted %s user. Please select the correct one to undelete and specify with "gam undelete user uid:"' % user) print('') @@ -11270,13 +11012,13 @@ def doUndeleteUser(): else: user_uid = matching_users[0]['id'] print("Undeleting account for %s" % user) - callGAPI(cd.users(), 'undelete', userKey=user_uid, body={'orgUnitPath': orgUnit}) + gapi.call(cd.users(), 'undelete', userKey=user_uid, body={'orgUnitPath': orgUnit}) def doDeleteGroup(): cd = buildGAPIObject('directory') group = normalizeEmailAddressOrUID(sys.argv[3]) print("Deleting group %s" % group) - callGAPI(cd.groups(), 'delete', groupKey=group) + gapi.call(cd.groups(), 'delete', groupKey=group) def doDeleteAlias(alias_email=None): cd = buildGAPIObject('directory') @@ -11293,27 +11035,27 @@ def doDeleteAlias(alias_email=None): print("Deleting alias %s" % alias_email) if is_user or (not is_user and not is_group): try: - callGAPI(cd.users().aliases(), 'delete', throw_reasons=[GAPI_INVALID, GAPI_BAD_REQUEST, GAPI_NOT_FOUND], userKey=alias_email, alias=alias_email) + gapi.call(cd.users().aliases(), 'delete', throw_reasons=[gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST, gapi.errors.ErrorReason.NOT_FOUND], userKey=alias_email, alias=alias_email) return - except (GAPI_invalid, GAPI_badRequest): + except (gapi.errors.GapiInvalidError, gapi.errors.GapiBadRequestError): pass - except GAPI_notFound: - systemErrorExit(4, 'The alias %s does not exist' % alias_email) + except gapi.errors.GapiNotFoundError: + controlflow.system_error_exit(4, 'The alias %s does not exist' % alias_email) if not is_user or (not is_user and not is_group): - callGAPI(cd.groups().aliases(), 'delete', groupKey=alias_email, alias=alias_email) + gapi.call(cd.groups().aliases(), 'delete', groupKey=alias_email, alias=alias_email) def doDeleteResourceCalendar(): resId = sys.argv[3] cd = buildGAPIObject('directory') print("Deleting resource calendar %s" % resId) - callGAPI(cd.resources().calendars(), 'delete', + gapi.call(cd.resources().calendars(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId) def doDeleteOrg(): cd = buildGAPIObject('directory') name = getOrgUnitItem(sys.argv[3]) print("Deleting organization %s" % name) - callGAPI(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(name))) + gapi.call(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(name))) def send_email(subject, body, recipient=None, sender=None, user=None, method='send', labels=None, msgHeaders={}, kwargs={}): api_body = {} @@ -11353,7 +11095,7 @@ def send_email(subject, body, recipient=None, sender=None, user=None, method='se elif method in ['insert', 'import']: if method == 'import': method = 'import_' - callGAPI(resource, method, userId=userId, body=api_body, **kwargs) + gapi.call(resource, method, userId=userId, body=api_body, **kwargs) def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList): fields = fieldsChoiceMap[fieldName.lower()] @@ -11488,7 +11230,7 @@ def headerFilterMatch(title): if GC_Values[GC_CSV_HEADER_FILTER]: titles = [t for t in titles if headerFilterMatch(t)] if not titles: - systemErrorExit(3, 'No columns selected with GAM_CSV_HEADER_FILTER\n') + controlflow.system_error_exit(3, 'No columns selected with GAM_CSV_HEADER_FILTER\n') return csv.register_dialect('nixstdout', lineterminator='\n') if todrive: @@ -11500,7 +11242,7 @@ def headerFilterMatch(title): writer.writerow(dict((item, item) for item in writer.fieldnames)) writer.writerows(csvRows) except IOError as e: - systemErrorExit(6, e) + controlflow.system_error_exit(6, e) if todrive: admin_email = _getValueFromOAuth('email') _, drive = buildDrive3GAPIObject(admin_email) @@ -11511,7 +11253,7 @@ def headerFilterMatch(title): and follow recommend steps to authorize GAM for Drive access.''' % (admin_email)) sys.exit(5) - result = callGAPI(drive.about(), 'get', fields='maxImportSizes') + result = gapi.call(drive.about(), 'get', fields='maxImportSizes') columns = len(titles) rows = len(csvRows) cell_count = rows * columns @@ -11525,7 +11267,7 @@ def headerFilterMatch(title): body = {'description': QuotedArgumentList(sys.argv), 'name': '%s - %s' % (GC_Values[GC_DOMAIN], list_type), 'mimeType': mimeType} - result = callGAPI(drive.files(), 'create', fields='webViewLink', + result = gapi.call(drive.files(), 'create', fields='webViewLink', body=body, media_body=googleapiclient.http.MediaInMemoryUpload(write_to.getvalue().encode(), mimetype='text/csv')) @@ -11685,7 +11427,7 @@ def doPrintUsers(): elif myarg == 'orderby': orderBy = sys.argv[i+1] if orderBy.lower() not in ['email', 'familyname', 'givenname', 'firstname', 'lastname']: - systemErrorExit(2, 'orderby must be one of email, familyName, givenName; got %s' % orderBy) + controlflow.system_error_exit(2, 'orderby must be one of email, familyName, givenName; got %s' % orderBy) elif orderBy.lower() in ['familyname', 'lastname']: orderBy = 'familyName' elif orderBy.lower() in ['givenname', 'firstname']: @@ -11717,7 +11459,7 @@ def doPrintUsers(): if field in USER_ARGUMENT_TO_PROPERTY_MAP: addFieldToCSVfile(field, USER_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) else: - systemErrorExit(2, '%s is not a valid argument for "gam print users fields"' % field) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print users fields"' % field) i += 2 elif myarg == 'groups': getGroupFeed = True @@ -11729,7 +11471,7 @@ def doPrintUsers(): email_parts = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print users"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print users"' % sys.argv[i]) if fieldsList: fields = 'nextPageToken,users(%s)' % ','.join(set(fieldsList)).replace('.', '/') else: @@ -11797,8 +11539,8 @@ def doCreateAlertFeedback(): alertId = sys.argv[3] body = {'type': sys.argv[4].upper()} if body['type'] not in valid_types: - systemErrorExit(2, '%s is not a valid feedback value, expected one of: %s' % (body['type'], ', '.join(valid_types))) - callGAPI(ac.alerts().feedback(), 'create', alertId=alertId, body=body) + controlflow.system_error_exit(2, '%s is not a valid feedback value, expected one of: %s' % (body['type'], ', '.join(valid_types))) + gapi.call(ac.alerts().feedback(), 'create', alertId=alertId, body=body) def doDeleteOrUndeleteAlert(action): _, ac = buildAlertCenterGAPIObject(_getValueFromOAuth('email')) @@ -11806,7 +11548,7 @@ def doDeleteOrUndeleteAlert(action): kwargs = {} if action == 'undelete': kwargs['body'] = {} - callGAPI(ac.alerts(), action, alertId=alertId, **kwargs) + gapi.call(ac.alerts(), action, alertId=alertId, **kwargs) GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = { 'admincreated': ['adminCreated', 'Admin_Created'], @@ -11943,7 +11685,7 @@ def doPrintGroups(): for attrName in COLLABORATIVE_INBOX_ATTRIBUTES: addFieldToCSVfile(attrName, {attrName: [attrName]}, gsfieldsList, fieldsTitles, titles) else: - systemErrorExit(2, '%s is not a valid argument for "gam print groups fields"' % field) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print groups fields"' % field) i += 2 elif myarg in ['members', 'memberscount']: roles.append(ROLE_MEMBER) @@ -11964,7 +11706,7 @@ def doPrintGroups(): managersCountOnly = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print groups"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print groups"' % sys.argv[i]) cdfields = ','.join(set(cdfieldsList)) if gsfieldsList: getSettings = True @@ -12062,7 +11804,7 @@ def doPrintGroups(): group['Owners'] = memberDelimiter.join(ownersList) if getSettings and not GroupIsAbuseOrPostmaster(groupEmail): sys.stderr.write(" Retrieving Settings for group %s (%s/%s)...\r\n" % (groupEmail, i, count)) - settings = callGAPI(gs.groups(), 'get', + settings = gapi.call(gs.groups(), 'get', soft_errors=True, retry_reasons=['serviceLimit', 'invalid'], groupUniqueId=groupEmail, fields=gsfields) @@ -12114,7 +11856,7 @@ def doPrintOrgs(): fields += sys.argv[i+1].split(',') i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print orgs"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print orgs"' % sys.argv[i]) printGettingAllItems('Organizational Units', None) if fields: get_fields = ','.join(fields) @@ -12122,7 +11864,7 @@ def doPrintOrgs(): else: list_fields = None get_fields = None - orgs = callGAPI(cd.orgunits(), 'list', + orgs = gapi.call(cd.orgunits(), 'list', customerId=GC_Values[GC_CUSTOMER_ID], type=listType, orgUnitPath=orgUnitPath, fields=list_fields) if not 'organizationUnits' in orgs: topLevelOrgId = getTopLevelOrgId(cd, orgUnitPath) @@ -12138,7 +11880,7 @@ def doPrintOrgs(): missing_parents = set(parentOrgIds) - set(retrievedOrgIds) for missing_parent in missing_parents: try: - result = callGAPI(cd.orgunits(), 'get', throw_reasons=['required'], + result = gapi.call(cd.orgunits(), 'get', throw_reasons=['required'], customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=missing_parent, fields=get_fields) orgunits.append(result) except: @@ -12192,7 +11934,7 @@ def doPrintAliases(): doUsers = True i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print aliases"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print aliases"' % sys.argv[i]) if doUsers: for query in queries: printGettingAllItems('User Aliases', query) @@ -12261,7 +12003,7 @@ def doPrintGroupMembers(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: - systemErrorExit(2, '%s is not a valid role for "gam print group-members %s"' % (role, myarg)) + controlflow.system_error_exit(2, '%s is not a valid role for "gam print group-members %s"' % (role, myarg)) i += 2 elif myarg in ['group', 'groupns', 'groupsusp']: group_email = normalizeEmailAddressOrUID(sys.argv[i+1]) @@ -12278,7 +12020,7 @@ def doPrintGroupMembers(): includeDerivedMembership = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print group-members"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print group-members"' % sys.argv[i]) if not groups_to_get: groups_to_get = callGAPIpages(cd.groups(), 'list', 'groups', message_attribute='email', customer=customer, domain=usedomain, userKey=usemember, query=usequery, @@ -12304,27 +12046,27 @@ def doPrintGroupMembers(): if membernames and 'type' in member and 'id' in member: if member['type'] == 'USER': try: - mbinfo = callGAPI(cd.users(), 'get', - throw_reasons=[GAPI_USER_NOT_FOUND, GAPI_NOT_FOUND, GAPI_FORBIDDEN], + mbinfo = gapi.call(cd.users(), 'get', + throw_reasons=[gapi.errors.ErrorReason.USER_NOT_FOUND, gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.FORBIDDEN], userKey=member['id'], fields='name') memberName = mbinfo['name']['fullName'] - except (GAPI_userNotFound, GAPI_notFound, GAPI_forbidden): + except (gapi.errors.GapiUserNotFoundError, gapi.errors.GapiNotFoundError, gapi.errors.GapiForbiddenError): memberName = 'Unknown' elif member['type'] == 'GROUP': try: - mbinfo = callGAPI(cd.groups(), 'get', - throw_reasons=[GAPI_NOT_FOUND, GAPI_FORBIDDEN], + mbinfo = gapi.call(cd.groups(), 'get', + throw_reasons=[gapi.errors.ErrorReason.NOT_FOUND, gapi.errors.ErrorReason.FORBIDDEN], groupKey=member['id'], fields='name') memberName = mbinfo['name'] - except (GAPI_notFound, GAPI_forbidden): + except (gapi.errors.GapiNotFoundError, gapi.errors.GapiForbiddenError): memberName = 'Unknown' elif member['type'] == 'CUSTOMER': try: - mbinfo = callGAPI(cd.customers(), 'get', - throw_reasons=[GAPI_BAD_REQUEST, GAPI_RESOURCE_NOT_FOUND, GAPI_FORBIDDEN], + mbinfo = gapi.call(cd.customers(), 'get', + throw_reasons=[gapi.errors.ErrorReason.BAD_REQUEST, gapi.errors.ErrorReason.RESOURCE_NOT_FOUND, gapi.errors.ErrorReason.FORBIDDEN], customerKey=member['id'], fields='customerDomain') memberName = mbinfo['customerDomain'] - except (GAPI_badRequest, GAPI_resourceNotFound, GAPI_forbidden): + except (gapi.errors.GapiBadRequestError, gapi.errors.GapiResourceNotFoundError, gapi.errors.GapiForbiddenError): memberName = 'Unknown' else: memberName = 'Unknown' @@ -12349,7 +12091,7 @@ def doPrintVaultMatters(): view = PROJECTION_CHOICES_MAP[myarg] i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam print matters"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam print matters"' % myarg) printGettingAllItems('Vault Matters', None) page_message = 'Got %%total_items%% Vault Matters...\n' matters = callGAPIpages(v.matters(), 'list', 'matters', page_message=page_message, view=view) @@ -12376,7 +12118,7 @@ def doPrintVaultExports(): matters = sys.argv[i+1].split(',') i += 2 else: - systemErrorExit(3, '%s is not a valid a valid argument to "gam print exports"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid a valid argument to "gam print exports"' % myarg) if not matters: matters_results = callGAPIpages(v.matters(), 'list', 'matters', view='BASIC', fields='matters(matterId,state),nextPageToken') for matter in matters_results: @@ -12413,7 +12155,7 @@ def doPrintVaultHolds(): matters = sys.argv[i+1].split(',') i += 2 else: - systemErrorExit(3, '%s is not a valid a valid argument to "gam print holds"' % myarg) + controlflow.system_error_exit(3, '%s is not a valid a valid argument to "gam print holds"' % myarg) if not matters: matters_results = callGAPIpages(v.matters(), 'list', 'matters', view='BASIC', fields='matters(matterId,state),nextPageToken') for matter in matters_results: @@ -12468,7 +12210,7 @@ def doPrintMobileDevices(): orderBy = sys.argv[i+1].lower() allowed_values = ['deviceid', 'email', 'lastsync', 'model', 'name', 'os', 'status', 'type'] if orderBy.lower() not in allowed_values: - systemErrorExit(2, 'orderBy must be one of %s; got %s' % (', '.join(allowed_values), orderBy)) + controlflow.system_error_exit(2, 'orderBy must be one of %s; got %s' % (', '.join(allowed_values), orderBy)) elif orderBy == 'lastsync': orderBy = 'lastSync' elif orderBy == 'deviceid': @@ -12481,7 +12223,7 @@ def doPrintMobileDevices(): projection = PROJECTION_CHOICES_MAP[myarg] i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print mobile"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print mobile"' % sys.argv[i]) for query in queries: printGettingAllItems('Mobile Devices', query) page_message = 'Got %%total_items%% Mobile Devices...\n' @@ -12586,7 +12328,7 @@ def doPrintCrosActivity(): delimiter = sys.argv[i+1] i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print crosactivity"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print crosactivity"' % sys.argv[i]) if not selectActiveTimeRanges and not selectDeviceFiles and not selectRecentUsers: selectActiveTimeRanges = selectRecentUsers = True if selectRecentUsers: @@ -12719,7 +12461,7 @@ def _getSelectedLists(myarg): orderBy = sys.argv[i+1].lower().replace('_', '') allowed_values = ['location', 'user', 'lastsync', 'notes', 'serialnumber', 'status', 'supportenddate'] if orderBy not in allowed_values: - systemErrorExit(2, 'orderBy must be one of %s; got %s' % (', '.join(allowed_values), orderBy)) + controlflow.system_error_exit(2, 'orderBy must be one of %s; got %s' % (', '.join(allowed_values), orderBy)) elif orderBy == 'location': orderBy = 'annotatedLocation' elif orderBy == 'user': @@ -12764,10 +12506,10 @@ def _getSelectedLists(myarg): elif field in CROS_ARGUMENT_TO_PROPERTY_MAP: addFieldToFieldsList(field, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList) else: - systemErrorExit(2, '%s is not a valid argument for "gam print cros fields"' % field) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print cros fields"' % field) i += 2 else: - systemErrorExit(2, '%s is not a valid argument for "gam print cros"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print cros"' % sys.argv[i]) if selectedLists: noLists = False projection = 'FULL' @@ -12893,7 +12635,7 @@ def doPrintLicenses(returnFields=None, skus=None, countsOnly=False, returnCounts countsOnly = True i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print licenses"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print licenses"' % sys.argv[i]) if not countsOnly: fields = 'nextPageToken,items(productId,skuId,userId)' titles = ['userId', 'productId', 'skuId'] @@ -12914,12 +12656,12 @@ def doPrintLicenses(returnFields=None, skus=None, countsOnly=False, returnCounts product = products[0] page_message = 'Got %%%%total_items%%%% Licenses for %s...\n' % SKUS.get(sku, {'displayName': sku})['displayName'] try: - licenses += callGAPIpages(lic.licenseAssignments(), 'listForProductAndSku', 'items', throw_reasons=[GAPI_INVALID, GAPI_FORBIDDEN], page_message=page_message, + licenses += callGAPIpages(lic.licenseAssignments(), 'listForProductAndSku', 'items', throw_reasons=[gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.FORBIDDEN], page_message=page_message, customerId=GC_Values[GC_DOMAIN], productId=product, skuId=sku, fields=fields) if countsOnly: licenseCounts.append(['Product', product, 'SKU', sku, 'Licenses', len(licenses)]) licenses = [] - except (GAPI_invalid, GAPI_forbidden): + except (gapi.errors.GapiInvalidError, gapi.errors.GapiForbiddenError): pass else: if not products: @@ -12927,12 +12669,12 @@ def doPrintLicenses(returnFields=None, skus=None, countsOnly=False, returnCounts for productId in products: page_message = 'Got %%%%total_items%%%% Licenses for %s...\n' % PRODUCTID_NAME_MAPPINGS.get(productId, productId) try: - licenses += callGAPIpages(lic.licenseAssignments(), 'listForProduct', 'items', throw_reasons=[GAPI_INVALID, GAPI_FORBIDDEN], page_message=page_message, + licenses += callGAPIpages(lic.licenseAssignments(), 'listForProduct', 'items', throw_reasons=[gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.FORBIDDEN], page_message=page_message, customerId=GC_Values[GC_DOMAIN], productId=productId, fields=fields) if countsOnly: licenseCounts.append(['Product', productId, 'Licenses', len(licenses)]) licenses = [] - except (GAPI_invalid, GAPI_forbidden): + except (gapi.errors.GapiInvalidError, gapi.errors.GapiForbiddenError): pass if countsOnly: if returnCounts: @@ -13026,7 +12768,7 @@ def doPrintFeatures(): fieldsList.append(possible_fields['feature'+myarg]) i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam print features"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam print features"' % sys.argv[i]) if fields: fields = fields % ','.join(fieldsList) features = callGAPIpages(cd.resources().features(), 'list', 'features', @@ -13073,7 +12815,7 @@ def doPrintBuildings(): fieldsList.append(possible_fields['building'+myarg]) i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam print buildings"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam print buildings"' % sys.argv[i]) if fields: fields = fields % ','.join(fieldsList) buildings = callGAPIpages(cd.resources().buildings(), 'list', 'buildings', @@ -13122,7 +12864,7 @@ def doPrintResourceCalendars(): addFieldToCSVfile(myarg, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 else: - systemErrorExit(2, '%s is not a valid argument for "gam print resources"' % sys.argv[i]) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print resources"' % sys.argv[i]) if not fieldsList: for field in RESCAL_DFLTFIELDS: addFieldToCSVfile(field, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) @@ -13216,7 +12958,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No ou = makeOrgUnitPathAbsolute(entity) users = [] if ou.startswith('id:'): - ou = callGAPI(cd.orgunits(), 'get', + ou = gapi.call(cd.orgunits(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=ou, fields='orgUnitPath')['orgUnitPath'] query = orgUnitPathQuery(ou, checkSuspended) page_message = None @@ -13289,12 +13031,12 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No elif entity_type in ['csv', 'csvfile', 'croscsv', 'croscsvfile']: drive, filenameColumn = os.path.splitdrive(entity) if filenameColumn.find(':') == -1: - systemErrorExit(2, 'Expected {0} FileName:FieldName'.format(entity_type)) + controlflow.system_error_exit(2, 'Expected {0} FileName:FieldName'.format(entity_type)) (filename, column) = filenameColumn.split(':') f = openFile(drive+filename) input_file = csv.DictReader(f, restval='') if column not in input_file.fieldnames: - csvFieldErrorExit(column, input_file.fieldnames) + controlflow.csv_field_error_exit(column, input_file.fieldnames) users = [] for row in input_file: user = row[column].strip() @@ -13348,7 +13090,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No if not silent: sys.stderr.write("done getting %s CrOS Devices.\r\n" % len(users)) else: - systemErrorExit(3, '%s is not a valid argument for "gam all"' % entity) + controlflow.system_error_exit(3, '%s is not a valid argument for "gam all"' % entity) elif entity_type == 'cros': users = entity.replace(',', ' ').split() entity = 'cros' @@ -13377,7 +13119,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No sys.stderr.write("done.\r\n") entity = 'cros' else: - systemErrorExit(2, '%s is not a valid argument for "gam"' % entity_type) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam"' % entity_type) full_users = list() if entity != 'cros' and not got_uids: for user in users: @@ -13408,13 +13150,13 @@ def OAuthInfo(): show_secret = True i += 1 else: - systemErrorExit(3, '%s is not a valid argument to "gam oauth info"' % sys.argv[i]) + controlflow.system_error_exit(3, '%s is not a valid argument to "gam oauth info"' % sys.argv[i]) if not access_token and not id_token: credentials = getValidOauth2TxtCredentials() access_token = credentials.token print("\nOAuth File: %s" % GC_Values[GC_OAUTH2_TXT]) oa2 = buildGAPIObject('oauth2') - token_info = callGAPI(oa2, 'tokeninfo', access_token=access_token, id_token=id_token) + token_info = gapi.call(oa2, 'tokeninfo', access_token=access_token, id_token=id_token) if 'issued_to' in token_info: print('Client ID: %s' % token_info['issued_to']) if credentials is not None and show_secret: @@ -13439,7 +13181,7 @@ def doDeleteOAuth(): credentials = getOauth2TxtStorageCredentials() if credentials is None: return - simplehttp = _createHttpObj() + simplehttp = gapi.create_http() params = {'token': credentials.refresh_token} revoke_uri = 'https://accounts.google.com/o/oauth2/revoke?%s' % urlencode(params) sys.stderr.write('This OAuth token will self-destruct in 3...') @@ -13474,7 +13216,7 @@ def writeCredentials(creds): } expected_iss = ['https://accounts.google.com', 'accounts.google.com'] if _getValueFromOAuth('iss', creds) not in expected_iss: - systemErrorExit(13, 'Wrong OAuth 2.0 credentials issuer. Got %s, expected one of %s' % (_getValueFromOAuth('iss', creds), ', '.join(expected_iss))) + controlflow.system_error_exit(13, 'Wrong OAuth 2.0 credentials issuer. Got %s, expected one of %s' % (_getValueFromOAuth('iss', creds), ', '.join(expected_iss))) creds_data['decoded_id_token'] = GC_Values[GC_DECODED_ID_TOKEN] data = json.dumps(creds_data, indent=2, sort_keys=True) writeFile(GC_Values[GC_OAUTH2_TXT], data) @@ -13484,7 +13226,7 @@ def doRequestOAuth(login_hint=None): if credentials is None or not credentials.valid: scopes = getScopesFromUser() if scopes is None: - systemErrorExit(0, '') + controlflow.system_error_exit(0, '') client_id, client_secret = getOAuthClientIDAndSecret() login_hint = _getValidateLoginHint(login_hint) # Needs to be set so oauthlib doesn't puke when Google changes our scopes @@ -13503,7 +13245,7 @@ def getOAuthClientIDAndSecret(): filename = GC_Values[GC_CLIENT_SECRETS_JSON] cs_data = readFile(filename, continueOnError=True, displayError=True) if not cs_data: - systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE) + controlflow.system_error_exit(14, MISSING_CLIENT_SECRETS_MESSAGE) try: cs_json = json.loads(cs_data) client_id = cs_json['installed']['client_id'] @@ -13512,7 +13254,7 @@ def getOAuthClientIDAndSecret(): client_id = re.sub(r'\.apps\.googleusercontent\.com$', '', client_id) client_secret = cs_json['installed']['client_secret'] except (ValueError, IndexError, KeyError): - systemErrorExit(3, 'the format of your client secrets file:\n\n%s\n\n' + controlflow.system_error_exit(3, 'the format of your client secrets file:\n\n%s\n\n' 'is incorrect. Please recreate the file.' % filename) return (client_id, client_secret) @@ -13617,7 +13359,7 @@ def getScopesFromUser(menu_options=None): try: menu.run() except ScopeSelectionMenu.UserRequestedExitException: - systemErrorExit(0, '') + controlflow.system_error_exit(0, '') return menu.get_selected_scopes() @@ -14139,7 +13881,7 @@ def getSubFields(i, fieldNames): subFields.setdefault(GAM_argvI, []) subFields[GAM_argvI].append((fieldName, match.start(), match.end())) else: - csvFieldErrorExit(fieldName, fieldNames) + controlflow.csv_field_error_exit(fieldName, fieldNames) pos = match.end() GAM_argv.append(myarg) elif myarg[0] == '~': @@ -14148,7 +13890,7 @@ def getSubFields(i, fieldNames): subFields[GAM_argvI] = [(fieldName, 0, len(myarg))] GAM_argv.append(myarg) else: - csvFieldErrorExit(fieldName, fieldNames) + controlflow.csv_field_error_exit(fieldName, fieldNames) else: GAM_argv.append(myarg) GAM_argvI += 1 @@ -14222,17 +13964,17 @@ def ProcessGAMCommand(args): run_batch(items) sys.exit(0) else: - systemErrorExit(2, 'batch file: {0}, not processed, {1} error{2}'.format(filename, errors, ['', 's'][errors != 1])) + controlflow.system_error_exit(2, 'batch file: {0}, not processed, {1} error{2}'.format(filename, errors, ['', 's'][errors != 1])) elif command == 'csv': if httplib2.debuglevel > 0: - systemErrorExit(1, 'CSV commands are not compatible with debug. Delete debug.gam and try again.') + controlflow.system_error_exit(1, 'CSV commands are not compatible with debug. Delete debug.gam and try again.') i = 2 filename = sys.argv[i] i, encoding = getCharSet(i+1) f = openFile(filename, encoding=encoding) csvFile = csv.DictReader(f) if (i == len(sys.argv)) or (sys.argv[i].lower() != 'gam') or (i+1 == len(sys.argv)): - systemErrorExit(3, '"gam csv " must be followed by a full GAM command...') + controlflow.system_error_exit(3, '"gam csv " must be followed by a full GAM command...') i += 1 GAM_argv, subFields = getSubFields(i, csvFile.fieldnames) items = [] @@ -14293,14 +14035,14 @@ def ProcessGAMCommand(args): elif argument in ['gcpfolder']: createGCPFolder() else: - systemErrorExit(2, '%s is not a valid argument for "gam create"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam create"' % argument) sys.exit(0) elif command == 'use': argument = sys.argv[2].lower() if argument in ['project', 'apiproject']: doUseProject() else: - systemErrorExit(2, '%s is not a valid argument for "gam use"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam use"' % argument) sys.exit(0) elif command == 'update': argument = sys.argv[2].lower() @@ -14345,7 +14087,7 @@ def ProcessGAMCommand(args): elif argument in ['feature']: doUpdateFeature() else: - systemErrorExit(2, '%s is not a valid argument for "gam update"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update"' % argument) sys.exit(0) elif command == 'info': argument = sys.argv[2].lower() @@ -14396,14 +14138,14 @@ def ProcessGAMCommand(args): elif argument in ['building']: doGetBuildingInfo() else: - systemErrorExit(2, '%s is not a valid argument for "gam info"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info"' % argument) sys.exit(0) elif command == 'cancel': argument = sys.argv[2].lower() if argument in ['guardianinvitation', 'guardianinvitations']: doCancelGuardianInvitation() else: - systemErrorExit(2, '%s is not a valid argument for "gam cancel"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam cancel"' % argument) sys.exit(0) elif command == 'delete': argument = sys.argv[2].lower() @@ -14450,7 +14192,7 @@ def ProcessGAMCommand(args): elif argument in ['alert']: doDeleteOrUndeleteAlert('delete') else: - systemErrorExit(2, '%s is not a valid argument for "gam delete"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam delete"' % argument) sys.exit(0) elif command == 'undelete': argument = sys.argv[2].lower() @@ -14461,7 +14203,7 @@ def ProcessGAMCommand(args): elif argument == 'alert': doDeleteOrUndeleteAlert('undelete') else: - systemErrorExit(2, '%s is not a valid argument for "gam undelete"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam undelete"' % argument) sys.exit(0) elif command in ['close', 'reopen']: # close and reopen will have to be split apart if either takes a new argument @@ -14469,7 +14211,7 @@ def ProcessGAMCommand(args): if argument in ['matter', 'vaultmatter']: doUpdateVaultMatter(action=command) else: - systemErrorExit(2, '%s is not a valid argument for "gam %s"' % (argument, command)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s"' % (argument, command)) sys.exit(0) elif command == 'print': argument = sys.argv[2].lower().replace('-', '') @@ -14536,7 +14278,7 @@ def ProcessGAMCommand(args): elif argument in ['alertfeedback', 'alertsfeedback']: doPrintShowAlertFeedback() else: - systemErrorExit(2, '%s is not a valid argument for "gam print"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print"' % argument) sys.exit(0) elif command == 'show': argument = sys.argv[2].lower() @@ -14549,7 +14291,7 @@ def ProcessGAMCommand(args): elif argument in ['project', 'projects']: doPrintShowProjects(False) else: - systemErrorExit(2, '%s is not a valid argument for "gam show"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show"' % argument) sys.exit(0) elif command in ['oauth', 'oauth2']: argument = sys.argv[2].lower() @@ -14566,11 +14308,11 @@ def ProcessGAMCommand(args): elif argument in ['refresh']: creds = getValidOauth2TxtCredentials(force_refresh=True) if not creds: - systemErrorExit(5, 'Credential refresh failed') + controlflow.system_error_exit(5, 'Credential refresh failed') else: print('Credentials refreshed') else: - systemErrorExit(2, '%s is not a valid argument for "gam oauth"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam oauth"' % argument) sys.exit(0) elif command == 'calendar': argument = sys.argv[3].lower() @@ -14597,7 +14339,7 @@ def ProcessGAMCommand(args): elif argument == 'modify': doCalendarModifySettings() else: - systemErrorExit(2, '%s is not a valid argument for "gam calendar"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam calendar"' % argument) sys.exit(0) elif command == 'printer': if sys.argv[2].lower() == 'register': @@ -14611,7 +14353,7 @@ def ProcessGAMCommand(args): elif argument in ['del', 'delete', 'remove']: doPrinterDelACL() else: - systemErrorExit(2, '%s is not a valid argument for "gam printer..."' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam printer..."' % argument) sys.exit(0) elif command == 'printjob': argument = sys.argv[3].lower() @@ -14626,7 +14368,7 @@ def ProcessGAMCommand(args): elif argument == 'resubmit': doPrintJobResubmit() else: - systemErrorExit(2, '%s is not a valid argument for "gam printjob"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam printjob"' % argument) sys.exit(0) elif command == 'report': showReport() @@ -14643,7 +14385,7 @@ def ProcessGAMCommand(args): elif argument == 'sync': doSyncCourseParticipants() else: - systemErrorExit(2, '%s is not a valid argument for "gam course"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam course"' % argument) sys.exit(0) elif command == 'download': argument = sys.argv[2].lower() @@ -14652,7 +14394,7 @@ def ProcessGAMCommand(args): elif argument in ['storagebucket']: doDownloadCloudStorageBucket() else: - systemErrorExit(2, '%s is not a valid argument for "gam download"' % argument) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam download"' % argument) sys.exit(0) users = getUsersToModify() command = sys.argv[3].lower() @@ -14669,7 +14411,7 @@ def ProcessGAMCommand(args): elif transferWhat == 'seccals': transferSecCals(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam transfer"' % transferWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam transfer"' % transferWhat) elif command == 'show': showWhat = sys.argv[4].lower() if showWhat in ['labels', 'label']: @@ -14731,7 +14473,7 @@ def ProcessGAMCommand(args): elif showWhat in ['teamdriveinfo']: doGetTeamDriveInfo(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam show"' % showWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam show"' % showWhat) elif command == 'print': printWhat = sys.argv[4].lower() if printWhat == 'calendars': @@ -14759,7 +14501,7 @@ def ProcessGAMCommand(args): elif printWhat in ['teamdrive', 'teamdrives']: printShowTeamDrives(users, True) else: - systemErrorExit(2, '%s is not a valid argument for "gam print"' % printWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam print"' % printWhat) elif command == 'modify': modifyWhat = sys.argv[4].lower() if modifyWhat in ['message', 'messages']: @@ -14767,7 +14509,7 @@ def ProcessGAMCommand(args): elif modifyWhat in ['thread', 'threads']: doProcessMessagesOrThreads(users, 'modify', 'threads') else: - systemErrorExit(2, '%s is not a valid argument for "gam modify"' % modifyWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam modify"' % modifyWhat) elif command == 'trash': trashWhat = sys.argv[4].lower() if trashWhat in ['message', 'messages']: @@ -14775,7 +14517,7 @@ def ProcessGAMCommand(args): elif trashWhat in ['thread', 'threads']: doProcessMessagesOrThreads(users, 'trash', 'threads') else: - systemErrorExit(2, '%s is not a valid argument for "gam trash"' % trashWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam trash"' % trashWhat) elif command == 'untrash': untrashWhat = sys.argv[4].lower() if untrashWhat in ['message', 'messages']: @@ -14783,7 +14525,7 @@ def ProcessGAMCommand(args): elif untrashWhat in ['thread', 'threads']: doProcessMessagesOrThreads(users, 'untrash', 'threads') else: - systemErrorExit(2, '%s is not a valid argument for "gam untrash"' % untrashWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam untrash"' % untrashWhat) elif command in ['delete', 'del']: delWhat = sys.argv[4].lower() if delWhat == 'delegate': @@ -14827,14 +14569,14 @@ def ProcessGAMCommand(args): elif delWhat == 'teamdrive': doDeleteTeamDrive(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam delete"' % delWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam delete"' % delWhat) elif command in ['add', 'create']: addWhat = sys.argv[4].lower() if addWhat == 'calendar': if command == 'add': addCalendar(users) else: - systemErrorExit(2, '%s is not implemented for "gam %s"' % (addWhat, command)) + controlflow.system_error_exit(2, '%s is not implemented for "gam %s"' % (addWhat, command)) elif addWhat == 'drivefile': createDriveFile(users) elif addWhat in ['license', 'licence']: @@ -14856,7 +14598,7 @@ def ProcessGAMCommand(args): elif addWhat == 'teamdrive': doCreateTeamDrive(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam %s"' % (addWhat, command)) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam %s"' % (addWhat, command)) elif command == 'update': updateWhat = sys.argv[4].lower() if updateWhat == 'calendar': @@ -14886,7 +14628,7 @@ def ProcessGAMCommand(args): elif updateWhat == 'teamdrive': doUpdateTeamDrive(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam update"' % updateWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam update"' % updateWhat) elif command in ['deprov', 'deprovision']: doDeprovUser(users) elif command == 'get': @@ -14896,13 +14638,13 @@ def ProcessGAMCommand(args): elif getWhat == 'drivefile': downloadDriveFile(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam get"' % getWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam get"' % getWhat) elif command == 'empty': emptyWhat = sys.argv[4].lower() if emptyWhat == 'drivetrash': doEmptyDriveTrash(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam empty"' % emptyWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam empty"' % emptyWhat) elif command == 'info': infoWhat = sys.argv[4].lower() if infoWhat == 'calendar': @@ -14914,13 +14656,13 @@ def ProcessGAMCommand(args): elif infoWhat == 'sendas': infoSendAs(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam info"' % infoWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam info"' % infoWhat) elif command == 'check': checkWhat = sys.argv[4].replace('_', '').lower() if checkWhat == 'serviceaccount': doCheckServiceAccount(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam check"' % checkWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam check"' % checkWhat) elif command == 'profile': doProfile(users) elif command == 'imap': @@ -14960,18 +14702,18 @@ def ProcessGAMCommand(args): if watchWhat == 'gmail': watchGmail(users) else: - systemErrorExit(2, '%s is not a valid argument for "gam watch"' % watchWhat) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam watch"' % watchWhat) else: - systemErrorExit(2, '%s is not a valid argument for "gam"' % command) + controlflow.system_error_exit(2, '%s is not a valid argument for "gam"' % command) except IndexError: showUsage() sys.exit(2) except KeyboardInterrupt: sys.exit(50) except socket.error as e: - systemErrorExit(3, str(e)) + controlflow.system_error_exit(3, str(e)) except MemoryError: - systemErrorExit(99, MESSAGE_GAM_OUT_OF_MEMORY) + controlflow.system_error_exit(99, MESSAGE_GAM_OUT_OF_MEMORY) except SystemExit as e: GM_Globals[GM_SYSEXITRC] = e.code return GM_Globals[GM_SYSEXITRC] @@ -14985,5 +14727,5 @@ def ProcessGAMCommand(args): # command line arguments mp_set_start_method('fork') if sys.version_info[0] < 3 or sys.version_info[1] < 5: - systemErrorExit(5, 'GAM requires Python 3.5 or newer. You are running %s.%s.%s. Please upgrade your Python version or use one of the binary GAM downloads.' % sys.version_info[:3]) + controlflow.system_error_exit(5, 'GAM requires Python 3.5 or newer. You are running %s.%s.%s. Please upgrade your Python version or use one of the binary GAM downloads.' % sys.version_info[:3]) sys.exit(ProcessGAMCommand(sys.argv)) diff --git a/src/gapi/__init__.py b/src/gapi/__init__.py new file mode 100644 index 000000000..2cbc28a9f --- /dev/null +++ b/src/gapi/__init__.py @@ -0,0 +1,168 @@ +"""Methods related to execution of GAPI requests.""" + +import controlflow +import display +from gapi import errors +import googleapiclient.errors +import httplib2 +from var import (GC_CA_FILE, GC_Values, GC_TLS_MIN_VERSION, GC_TLS_MAX_VERSION, + GM_Globals, GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER, + GM_EXTRA_ARGS_DICT, GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID, + MESSAGE_API_ACCESS_CONFIG, MESSAGE_API_ACCESS_DENIED, + MESSAGE_SERVICE_NOT_APPLICABLE) +import google.auth.exceptions + + +def create_http(cache=None, + timeout=None, + override_min_tls=None, + override_max_tls=None): + """Creates a uniform HTTP transport object. + + Args: + cache: The HTTP cache to use. + timeout: The cache timeout, in seconds. + override_min_tls: The minimum TLS version to require. If not provided, the + default is used. + override_max_tls: The maximum TLS version to require. If not provided, the + default is used. + + Returns: + httplib2.Http with the specified options. + """ + tls_minimum_version = override_min_tls if override_min_tls else GC_Values[ + GC_TLS_MIN_VERSION] + tls_maximum_version = override_max_tls if override_max_tls else GC_Values[ + GC_TLS_MAX_VERSION] + return httplib2.Http( + ca_certs=GC_Values[GC_CA_FILE], + tls_maximum_version=tls_maximum_version, + tls_minimum_version=tls_minimum_version, + cache=cache, + timeout=timeout) + + +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(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) + else: + 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('{0}: {1} - {2}{3}'.format(http_status, message, + reason, + ['', + ': Giving up.'][n > 1])) + return None + controlflow.system_error_exit( + int(http_status), '{0}: {1} - {2}'.format(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('User {0}: {1}'.format( + GM_Globals[GM_CURRENT_API_USER], str(e))) + return None + except ValueError as e: + if 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)) + + +# TODO: Make this private once all execution related items that use this method +# have been brought into this file +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, + 'Authentication Token Error - {0}'.format(e)) diff --git a/src/gapi/__init___test.py b/src/gapi/__init___test.py new file mode 100644 index 000000000..52cc61c8b --- /dev/null +++ b/src/gapi/__init___test.py @@ -0,0 +1,240 @@ +"""Tests for gapi.""" + +import json +import unittest +from unittest.mock import MagicMock +from unittest.mock import patch + +from gam import SetGlobalVariables +import gapi +from gapi import errors + + +def create_http_error(status, reason, message): + """Creates a HttpError object similar to most Google API Errors. + + Args: + status: Int, the error's HTTP response status number. + reason: String, a camelCase reason for the HttpError being given. + message: String, a general error message describing the error that occurred. + + Returns: + googleapiclient.errors.HttpError + """ + response = { + 'status': status, + 'content-type': 'application/json', + } + content = { + 'error': { + 'code': status, + 'errors': [{ + 'reason': str(reason), + 'message': message, + }] + } + } + content_bytes = json.dumps(content).encode('UTF-8') + return gapi.googleapiclient.errors.HttpError(response, content_bytes) + + +class CreateHttpTest(unittest.TestCase): + + def setUp(self): + SetGlobalVariables() + super(CreateHttpTest, self).setUp() + + def test_create_http_sets_default_values_on_http(self): + http = gapi.create_http() + self.assertIsNone(http.cache) + self.assertIsNone(http.timeout) + self.assertEqual(http.tls_minimum_version, + gapi.GC_Values[gapi.GC_TLS_MIN_VERSION]) + self.assertEqual(http.tls_maximum_version, + gapi.GC_Values[gapi.GC_TLS_MAX_VERSION]) + self.assertEqual(http.ca_certs, gapi.GC_Values[gapi.GC_CA_FILE]) + + def test_create_http_sets_tls_min_version(self): + http = gapi.create_http(override_min_tls=1111) + self.assertEqual(http.tls_minimum_version, 1111) + + def test_create_http_sets_tls_max_version(self): + http = gapi.create_http(override_max_tls=9999) + self.assertEqual(http.tls_maximum_version, 9999) + + def test_create_http_sets_cache(self): + fake_cache = {} + http = gapi.create_http(cache=fake_cache) + self.assertEqual(http.cache, fake_cache) + + def test_create_http_sets_cache_timeout(self): + http = gapi.create_http(timeout=1234) + self.assertEqual(http.timeout, 1234) + + +class CallTest(unittest.TestCase): + + def setUp(self): + SetGlobalVariables() + self.mock_service = MagicMock() + self.mock_method_name = 'mock_method' + self.mock_method = getattr(self.mock_service, self.mock_method_name) + super(CallTest, self).setUp() + + def test_call_returns_basic_200_response(self): + response = gapi.call(self.mock_service, self.mock_method_name) + self.assertEqual(response, self.mock_method().execute.return_value) + + def test_call_passes_target_method_params(self): + gapi.call( + self.mock_service, self.mock_method_name, my_param_1=1, my_param_2=2) + self.mock_method.assert_called_once() + method_kwargs = self.mock_method.call_args[1] + self.assertEqual(1, method_kwargs.get('my_param_1')) + self.assertEqual(2, method_kwargs.get('my_param_2')) + + @patch.object(gapi.errors, 'get_gapi_error_detail') + def test_call_retries_with_soft_errors(self, mock_error_detail): + mock_error_detail.return_value = (-1, 'aReason', 'some message') + + # Make the request fail first, then return the proper response on the retry. + fake_http_error = create_http_error(403, 'aReason', 'unused message') + fake_200_response = MagicMock() + self.mock_method.return_value.execute.side_effect = [ + fake_http_error, fake_200_response + ] + + response = gapi.call( + self.mock_service, self.mock_method_name, soft_errors=True) + self.assertEqual(response, fake_200_response) + self.mock_service._http.request.credentials.refresh.assert_called_once() + self.assertEqual(self.mock_method.return_value.execute.call_count, 2) + + def test_call_throws_for_provided_reason(self): + throw_reason = errors.ErrorReason.USER_NOT_FOUND + fake_http_error = create_http_error(404, throw_reason, 'forced throw') + self.mock_method.return_value.execute.side_effect = fake_http_error + + gam_exception = errors.ERROR_REASON_TO_EXCEPTION[throw_reason] + with self.assertRaises(gam_exception): + gapi.call( + self.mock_service, + self.mock_method_name, + throw_reasons=[throw_reason]) + + # Prevent wait_on_failure from performing actual backoff unnecessarily, since + # we're not actually testing over a network connection + @patch.object(gapi.controlflow, 'wait_on_failure') + def test_call_retries_request_for_default_retry_reasons( + self, mock_wait_on_failure): + + # Test using one of the default retry reasons + default_throw_reason = errors.ErrorReason.BACKEND_ERROR + self.assertIn(default_throw_reason, errors.DEFAULT_RETRY_REASONS) + + fake_http_error = create_http_error(404, default_throw_reason, 'message') + fake_200_response = MagicMock() + # Fail once, then succeed on retry + self.mock_method.return_value.execute.side_effect = [ + fake_http_error, fake_200_response + ] + + response = gapi.call( + self.mock_service, self.mock_method_name, retry_reasons=[]) + self.assertEqual(response, fake_200_response) + self.assertEqual(self.mock_method.return_value.execute.call_count, 2) + # Make sure a backoff technique was used for retry. + mock_wait_on_failure.assert_called_once() + + # Prevent wait_on_failure from performing actual backoff unnecessarily, since + # we're not actually testing over a network connection + @patch.object(gapi.controlflow, 'wait_on_failure') + def test_call_retries_requests_for_provided_retry_reasons( + self, unused_mock_wait_on_failure): + + retry_reason1 = errors.ErrorReason.INTERNAL_ERROR + fake_retrieable_error1 = create_http_error(400, retry_reason1, + 'Forced Error 1') + retry_reason2 = errors.ErrorReason.SYSTEM_ERROR + fake_retrieable_error2 = create_http_error(400, retry_reason2, + 'Forced Error 2') + non_retriable_reason = errors.ErrorReason.SERVICE_NOT_AVAILABLE + fake_non_retriable_error = create_http_error( + 400, non_retriable_reason, + 'This error should not cause the request to be retried') + # Fail once, then succeed on retry + self.mock_method.return_value.execute.side_effect = [ + fake_retrieable_error1, fake_retrieable_error2, fake_non_retriable_error + ] + + with self.assertRaises(SystemExit): + # The third call should raise the SystemExit when non_retriable_error is + # raised. + gapi.call( + self.mock_service, + self.mock_method_name, + retry_reasons=[retry_reason1, retry_reason2]) + + self.assertEqual(self.mock_method.return_value.execute.call_count, 3) + + def test_call_exits_on_oauth_token_error(self): + # An error with any OAUTH2_TOKEN_ERROR + fake_token_error = gapi.google.auth.exceptions.RefreshError( + errors.OAUTH2_TOKEN_ERRORS[0]) + self.mock_method.return_value.execute.side_effect = fake_token_error + + with self.assertRaises(SystemExit): + gapi.call(self.mock_service, self.mock_method_name) + + def test_call_exits_on_nonretriable_error(self): + error_reason = 'unknownReason' + fake_http_error = create_http_error(500, error_reason, + 'Testing unretriable errors') + self.mock_method.return_value.execute.side_effect = fake_http_error + + with self.assertRaises(SystemExit): + gapi.call(self.mock_service, self.mock_method_name) + + def test_call_exits_on_request_valueerror(self): + self.mock_method.return_value.execute.side_effect = ValueError() + + with self.assertRaises(SystemExit): + gapi.call(self.mock_service, self.mock_method_name) + + def test_call_clears_bad_http_cache_on_request_failure(self): + self.mock_service._http.cache = 'something that is not None' + fake_200_response = MagicMock() + self.mock_method.return_value.execute.side_effect = [ + ValueError(), fake_200_response + ] + + self.assertIsNotNone(self.mock_service._http.cache) + response = gapi.call(self.mock_service, self.mock_method_name) + self.assertEqual(response, fake_200_response) + # Assert the cache was cleared + self.assertIsNone(self.mock_service._http.cache) + + # Prevent wait_on_failure from performing actual backoff unnecessarily, since + # we're not actually testing over a network connection + @patch.object(gapi.controlflow, 'wait_on_failure') + def test_call_retries_requests_with_backoff_on_servernotfounderror( + self, mock_wait_on_failure): + fake_servernotfounderror = gapi.httplib2.ServerNotFoundError() + fake_200_response = MagicMock() + # Fail once, then succeed on retry + self.mock_method.return_value.execute.side_effect = [ + fake_servernotfounderror, fake_200_response + ] + + http_connections = self.mock_service._http.connections + response = gapi.call(self.mock_service, self.mock_method_name) + self.assertEqual(response, fake_200_response) + # HTTP cached connections should be cleared on receiving this error + self.assertNotEqual(http_connections, self.mock_service._http.connections) + self.assertEqual(self.mock_method.return_value.execute.call_count, 2) + # Make sure a backoff technique was used for retry. + mock_wait_on_failure.assert_called_once() + + +if __name__ == '__main__': + unittest.main() diff --git a/src/gapi/errors.py b/src/gapi/errors.py new file mode 100644 index 000000000..7829e58e3 --- /dev/null +++ b/src/gapi/errors.py @@ -0,0 +1,340 @@ +"""GAPI and OAuth Token related errors methods.""" + +import json + +import controlflow +from enum import Enum +import googleapiclient.errors +from var import UTF8 +import display # TODO: Change to relative import when gam is setup as a package + + +class GapiAbortedError(Exception): + pass + + +class GapiAuthErrorError(Exception): + pass + + +class GapiBadRequestError(Exception): + pass + + +class GapiConditionNotMetError(Exception): + pass + + +class GapiCyclicMembershipsNotAllowedError(Exception): + pass + + +class GapiDomainCannotUseApisError(Exception): + pass + + +class GapiDomainNotFoundError(Exception): + pass + + +class GapiDuplicateError(Exception): + pass + + +class GapiFailedPreconditionError(Exception): + pass + + +class GapiForbiddenError(Exception): + pass + + +class GapiGroupNotFoundError(Exception): + pass + + +class GapiInvalidError(Exception): + pass + + +class GapiInvalidArgumentError(Exception): + pass + + +class GapiInvalidMemberError(Exception): + pass + + +class GapiMemberNotFoundError(Exception): + pass + + +class GapiNotFoundError(Exception): + pass + + +class GapiNotImplementedError(Exception): + pass + + +class GapiPermissionDeniedError(Exception): + pass + + +class GapiResourceNotFoundError(Exception): + pass + + +class GapiServiceNotAvailableError(Exception): + pass + + +class GapiUserNotFoundError(Exception): + pass + + +# GAPI Error Reasons +class ErrorReason(Enum): + """The reason why a non-200 HTTP response was returned from a GAPI.""" + ABORTED = 'aborted' + AUTH_ERROR = 'authError' + BACKEND_ERROR = 'backendError' + BAD_REQUEST = 'badRequest' + CONDITION_NOT_MET = 'conditionNotMet' + CYCLIC_MEMBERSHIPS_NOT_ALLOWED = 'cyclicMembershipsNotAllowed' + DOMAIN_CANNOT_USE_APIS = 'domainCannotUseApis' + DOMAIN_NOT_FOUND = 'domainNotFound' + DUPLICATE = 'duplicate' + FAILED_PRECONDITION = 'failedPrecondition' + FORBIDDEN = 'forbidden' + GROUP_NOT_FOUND = 'groupNotFound' + INTERNAL_ERROR = 'internalError' + INVALID = 'invalid' + INVALID_ARGUMENT = 'invalidArgument' + INVALID_MEMBER = 'invalidMember' + MEMBER_NOT_FOUND = 'memberNotFound' + NOT_FOUND = 'notFound' + NOT_IMPLEMENTED = 'notImplemented' + PERMISSION_DENIED = 'permissionDenied' + QUOTA_EXCEEDED = 'quotaExceeded' + RATE_LIMIT_EXCEEDED = 'rateLimitExceeded' + RESOURCE_NOT_FOUND = 'resourceNotFound' + SERVICE_NOT_AVAILABLE = 'serviceNotAvailable' + SYSTEM_ERROR = 'systemError' + USER_NOT_FOUND = 'userNotFound' + USER_RATE_LIMIT_EXCEEDED = 'userRateLimitExceeded' + + def __str__(self): + return str(self.value) + + +# Common sets of GAPI error reasons +DEFAULT_RETRY_REASONS = [ + ErrorReason.QUOTA_EXCEEDED, ErrorReason.RATE_LIMIT_EXCEEDED, + ErrorReason.USER_RATE_LIMIT_EXCEEDED, ErrorReason.BACKEND_ERROR, + ErrorReason.INTERNAL_ERROR +] +GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE] +GROUP_GET_THROW_REASONS = [ + ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND, + ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.FORBIDDEN, + ErrorReason.BAD_REQUEST +] +GROUP_GET_RETRY_REASONS = [ErrorReason.INVALID, ErrorReason.SYSTEM_ERROR] +MEMBERS_THROW_REASONS = [ + ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND, + ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.INVALID, + ErrorReason.FORBIDDEN +] +MEMBERS_RETRY_REASONS = [ErrorReason.SYSTEM_ERROR] + +# A map of GAPI error reasons to the corresponding GAM Python Exception +ERROR_REASON_TO_EXCEPTION = { + ErrorReason.ABORTED: + GapiAbortedError, + ErrorReason.AUTH_ERROR: + GapiAuthErrorError, + ErrorReason.BAD_REQUEST: + GapiBadRequestError, + ErrorReason.CONDITION_NOT_MET: + GapiConditionNotMetError, + ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED: + GapiCyclicMembershipsNotAllowedError, + ErrorReason.DOMAIN_CANNOT_USE_APIS: + GapiDomainCannotUseApisError, + ErrorReason.DOMAIN_NOT_FOUND: + GapiDomainNotFoundError, + ErrorReason.DUPLICATE: + GapiDuplicateError, + ErrorReason.FAILED_PRECONDITION: + GapiFailedPreconditionError, + ErrorReason.FORBIDDEN: + GapiForbiddenError, + ErrorReason.GROUP_NOT_FOUND: + GapiGroupNotFoundError, + ErrorReason.INVALID: + GapiInvalidError, + ErrorReason.INVALID_ARGUMENT: + GapiInvalidArgumentError, + ErrorReason.INVALID_MEMBER: + GapiInvalidMemberError, + ErrorReason.MEMBER_NOT_FOUND: + GapiMemberNotFoundError, + ErrorReason.NOT_FOUND: + GapiNotFoundError, + ErrorReason.NOT_IMPLEMENTED: + GapiNotImplementedError, + ErrorReason.PERMISSION_DENIED: + GapiPermissionDeniedError, + ErrorReason.RESOURCE_NOT_FOUND: + GapiResourceNotFoundError, + ErrorReason.SERVICE_NOT_AVAILABLE: + GapiServiceNotAvailableError, + ErrorReason.USER_NOT_FOUND: + GapiUserNotFoundError, +} + +# OAuth Token Errors +OAUTH2_TOKEN_ERRORS = [ + 'access_denied', + 'access_denied: Requested client not authorized', + 'internal_failure: Backend Error', + 'internal_failure: None', + 'invalid_grant', + 'invalid_grant: Bad Request', + 'invalid_grant: Invalid email or User ID', + 'invalid_grant: Not a valid email', + 'invalid_grant: Invalid JWT: No valid verifier found for issuer', + 'invalid_request: Invalid impersonation prn email address', + 'unauthorized_client: Client is unauthorized to retrieve access tokens ' + 'using this method', + 'unauthorized_client: Client is unauthorized to retrieve access tokens ' + 'using this method, or client not authorized for any of the scopes ' + 'requested', + 'unauthorized_client: Unauthorized client or scope in request', +] + + +def _create_http_error_dict(status_code, reason, message): + """Creates a basic error dict similar to most Google API Errors. + + Args: + status_code: Int, the error's HTTP response status code. + reason: String, a camelCase reason for the HttpError being given. + message: String, a general error message describing the error that occurred. + + Returns: + dict + """ + return { + 'error': { + 'code': status_code, + 'errors': [{ + 'reason': str(reason), + 'message': message, + }] + } + } + + +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) diff --git a/src/gapi/errors_test.py b/src/gapi/errors_test.py new file mode 100644 index 000000000..9b4a70669 --- /dev/null +++ b/src/gapi/errors_test.py @@ -0,0 +1,209 @@ +"""Python unit tests for gapi.errors""" + +import json + +import unittest +from unittest.mock import patch + +import googleapiclient.errors +from gapi import errors + + +def create_simple_http_error(status, reason, message): + content = errors._create_http_error_dict(status, reason, message) + return create_http_error(status, content) + + +def create_http_error(status, content): + response = { + 'status': status, + 'content-type': 'application/json', + } + content_as_bytes = json.dumps(content).encode('UTF-8') + return googleapiclient.errors.HttpError(response, content_as_bytes) + + +class ErrorsTest(unittest.TestCase): + + def test_get_gapi_error_detail_quota_exceeded(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_detail_invalid_domain(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_detail_invalid_signing_key(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_detail_unknown_error(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_retry_http_error(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_prints_soft_errors(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_exits_on_unrecoverable_errors(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_quota_exceeded_for_current_request(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_quota_exceeded_high_request_rate(self): + # TODO: Add test logic once the opening ValueError exception case has a + # repro case (i.e. an Exception type/format that will cause it to raise). + pass + + def test_get_gapi_error_extracts_user_not_found(self): + err = create_simple_http_error(404, 'notFound', + 'Resource Not Found: userKey.') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 404) + self.assertEqual(reason, errors.ErrorReason.USER_NOT_FOUND.value) + self.assertEqual(message, 'Resource Not Found: userKey.') + + def test_get_gapi_error_extracts_group_not_found(self): + err = create_simple_http_error(404, 'notFound', + 'Resource Not Found: groupKey.') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 404) + self.assertEqual(reason, errors.ErrorReason.GROUP_NOT_FOUND.value) + self.assertEqual(message, 'Resource Not Found: groupKey.') + + def test_get_gapi_error_extracts_member_not_found(self): + err = create_simple_http_error(404, 'notFound', + 'Resource Not Found: memberKey.') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 404) + self.assertEqual(reason, errors.ErrorReason.MEMBER_NOT_FOUND.value) + self.assertEqual(message, 'Resource Not Found: memberKey.') + + def test_get_gapi_error_extracts_domain_not_found(self): + err = create_simple_http_error(404, 'notFound', 'Domain not found.') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 404) + self.assertEqual(reason, errors.ErrorReason.DOMAIN_NOT_FOUND.value) + self.assertEqual(message, 'Domain not found.') + + def test_get_gapi_error_extracts_generic_resource_not_found(self): + err = create_simple_http_error(404, 'notFound', + 'Resource Not Found: unknownResource.') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 404) + self.assertEqual(reason, errors.ErrorReason.RESOURCE_NOT_FOUND.value) + self.assertEqual(message, 'Resource Not Found: unknownResource.') + + def test_get_gapi_error_extracts_invalid_userid(self): + err = create_simple_http_error(400, 'invalid', 'Invalid Input: userId') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, errors.ErrorReason.USER_NOT_FOUND.value) + self.assertEqual(message, 'Invalid Input: userId') + + def test_get_gapi_error_extracts_invalid_member(self): + err = create_simple_http_error(400, 'invalid', 'Invalid Input: memberKey') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, errors.ErrorReason.INVALID_MEMBER.value) + self.assertEqual(message, 'Invalid Input: memberKey') + + def test_get_gapi_error_extracts_bad_request(self): + err = create_simple_http_error(400, 'failedPrecondition', 'Bad Request') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, errors.ErrorReason.BAD_REQUEST.value) + self.assertEqual(message, 'Bad Request') + + def test_get_gapi_error_extracts_service_not_available(self): + err = create_simple_http_error(400, 'failedPrecondition', + 'Mail service not enabled') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, errors.ErrorReason.SERVICE_NOT_AVAILABLE.value) + self.assertEqual(message, 'Mail service not enabled') + + def test_get_gapi_error_extracts_required_member_not_found(self): + err = create_simple_http_error(400, 'required', + 'Missing required field: memberKey') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, errors.ErrorReason.MEMBER_NOT_FOUND.value) + self.assertEqual(message, 'Missing required field: memberKey') + + def test_get_gapi_error_extracts_cyclic_memberships_error(self): + err = create_simple_http_error(400, 'conditionNotMet', + 'Cyclic memberships not allowed') + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, 400) + self.assertEqual(reason, + errors.ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value) + self.assertEqual(message, 'Cyclic memberships not allowed') + + def test_get_gapi_error_extracts_single_error_with_message(self): + status_code = 999 + response = {'status': status_code} + # This error does not have an "errors" key describing each error. + content = {'error': {'code': status_code, 'message': 'unknown error'}} + content_as_bytes = json.dumps(content).encode('UTF-8') + err = errors.googleapiclient.errors.HttpError(response, content_as_bytes) + + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, status_code) + self.assertEqual(reason, str(status_code)) + self.assertEqual(message, content['error']['message']) + + def test_get_gapi_error_exits_code_4_on_malformed_error_with_unknown_description( + self): + status_code = 999 + response = {'status': status_code} + # This error only has an error_description_field and an unknown description. + content = {'error_description': 'something errored'} + content_as_bytes = json.dumps(content).encode('UTF-8') + err = errors.googleapiclient.errors.HttpError(response, content_as_bytes) + + with self.assertRaises(SystemExit) as context: + errors.get_gapi_error_detail(err) + self.assertEqual(4, context.exception.code) + + def test_get_gapi_error_exits_on_invalid_error_description(self): + status_code = 400 + response = {'status': status_code} + content = {'error_description': 'Invalid Value'} + content_as_bytes = json.dumps(content).encode('UTF-8') + err = errors.googleapiclient.errors.HttpError(response, content_as_bytes) + + http_status, reason, message = errors.get_gapi_error_detail(err) + self.assertEqual(http_status, status_code) + self.assertEqual(reason, errors.ErrorReason.INVALID.value) + self.assertEqual(message, 'Invalid Value') + + def test_get_gapi_error_exits_code_4_on_unexpected_error_contents(self): + status_code = 900 + response = {'status': status_code} + content = {'notErrorContentThatIsExpected': 'foo'} + content_as_bytes = json.dumps(content).encode('UTF-8') + err = errors.googleapiclient.errors.HttpError(response, content_as_bytes) + + with self.assertRaises(SystemExit) as context: + errors.get_gapi_error_detail(err) + self.assertEqual(4, context.exception.code) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/var.py b/src/var.py index 1b9db722e..1417107e1 100644 --- a/src/var.py +++ b/src/var.py @@ -951,58 +951,6 @@ MESSAGE_INSTRUCTIONS_OAUTH2SERVICE_JSON = 'Please run\n\ngam create project\ngam user check serviceaccount\n\nto create and configure a service account.' MESSAGE_UPDATE_GAM_TO_64BIT = "You're running a 32-bit version of GAM on a 64-bit version of Windows, upgrade to a windows-x86_64 version of GAM" MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY = 'Your system time differs from Google by %s' -# oauth errors -OAUTH2_TOKEN_ERRORS = [ - 'access_denied', - 'access_denied: Requested client not authorized', - 'internal_failure: Backend Error', - 'internal_failure: None', - 'invalid_grant', - 'invalid_grant: Bad Request', - 'invalid_grant: Invalid email or User ID', - 'invalid_grant: Not a valid email', - 'invalid_grant: Invalid JWT: No valid verifier found for issuer', - 'invalid_request: Invalid impersonation prn email address', - 'unauthorized_client: Client is unauthorized to retrieve access tokens using this method', - 'unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested', - 'unauthorized_client: Unauthorized client or scope in request', - ] -# -# callGAPI throw reasons -GAPI_ABORTED = 'aborted' -GAPI_AUTH_ERROR = 'authError' -GAPI_BACKEND_ERROR = 'backendError' -GAPI_BAD_REQUEST = 'badRequest' -GAPI_CONDITION_NOT_MET = 'conditionNotMet' -GAPI_CYCLIC_MEMBERSHIPS_NOT_ALLOWED = 'cyclicMembershipsNotAllowed' -GAPI_DOMAIN_CANNOT_USE_APIS = 'domainCannotUseApis' -GAPI_DOMAIN_NOT_FOUND = 'domainNotFound' -GAPI_DUPLICATE = 'duplicate' -GAPI_FAILED_PRECONDITION = 'failedPrecondition' -GAPI_FORBIDDEN = 'forbidden' -GAPI_GROUP_NOT_FOUND = 'groupNotFound' -GAPI_INTERNAL_ERROR = 'internalError' -GAPI_INVALID = 'invalid' -GAPI_INVALID_ARGUMENT = 'invalidArgument' -GAPI_INVALID_MEMBER = 'invalidMember' -GAPI_MEMBER_NOT_FOUND = 'memberNotFound' -GAPI_NOT_FOUND = 'notFound' -GAPI_NOT_IMPLEMENTED = 'notImplemented' -GAPI_PERMISSION_DENIED = 'permissionDenied' -GAPI_QUOTA_EXCEEDED = 'quotaExceeded' -GAPI_RATE_LIMIT_EXCEEDED = 'rateLimitExceeded' -GAPI_RESOURCE_NOT_FOUND = 'resourceNotFound' -GAPI_SERVICE_NOT_AVAILABLE = 'serviceNotAvailable' -GAPI_SYSTEM_ERROR = 'systemError' -GAPI_USER_NOT_FOUND = 'userNotFound' -GAPI_USER_RATE_LIMIT_EXCEEDED = 'userRateLimitExceeded' -# -GAPI_DEFAULT_RETRY_REASONS = [GAPI_QUOTA_EXCEEDED, GAPI_RATE_LIMIT_EXCEEDED, GAPI_USER_RATE_LIMIT_EXCEEDED, GAPI_BACKEND_ERROR, GAPI_INTERNAL_ERROR] -GAPI_GMAIL_THROW_REASONS = [GAPI_SERVICE_NOT_AVAILABLE] -GAPI_GROUP_GET_THROW_REASONS = [GAPI_GROUP_NOT_FOUND, GAPI_DOMAIN_NOT_FOUND, GAPI_DOMAIN_CANNOT_USE_APIS, GAPI_FORBIDDEN, GAPI_BAD_REQUEST] -GAPI_GROUP_GET_RETRY_REASONS = [GAPI_INVALID, GAPI_SYSTEM_ERROR] -GAPI_MEMBERS_THROW_REASONS = [GAPI_GROUP_NOT_FOUND, GAPI_DOMAIN_NOT_FOUND, GAPI_DOMAIN_CANNOT_USE_APIS, GAPI_INVALID, GAPI_FORBIDDEN] -GAPI_MEMBERS_RETRY_REASONS = [GAPI_SYSTEM_ERROR] USER_ADDRESS_TYPES = ['home', 'work', 'other'] USER_EMAIL_TYPES = ['home', 'work', 'other']