Auto-format all files using yapf and pre-commit (#1173)

Automatic fixes produced by yapf formatting using `--style=google`, as
well as common pre-commit checks such as trailing whitespace removal,
double quote fixer, and newlines at the end of a file.
This commit is contained in:
ejochman
2020-04-28 13:59:47 -07:00
committed by GitHub
parent 216f2920b9
commit 0bd4eefeca
35 changed files with 18967 additions and 14714 deletions

View File

@@ -9,10 +9,12 @@ repos:
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: double-quote-string-fixer
- id: check-yaml - id: check-yaml
- id: check-docstring-first - id: check-docstring-first
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: check-merge-conflict
- repo: https://github.com/pre-commit/mirrors-yapf - repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.29.0 rev: v0.29.0

View File

@@ -7,5 +7,5 @@ import sys
from gam.__main__ import main from gam.__main__ import main
# Run from command line # Run from command line
if __name__ == "__main__": if __name__ == '__main__':
main(sys.argv) main(sys.argv)

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,7 @@ from multiprocessing import set_start_method
from gam import controlflow from gam import controlflow
import gam import gam
def main(argv): def main(argv):
freeze_support() freeze_support()
if sys.platform == 'darwin': if sys.platform == 'darwin':
@@ -37,11 +38,13 @@ def main(argv):
# command line arguments # command line arguments
set_start_method('fork') set_start_method('fork')
if sys.version_info[0] < 3 or sys.version_info[1] < 6: if sys.version_info[0] < 3 or sys.version_info[1] < 6:
controlflow.system_error_exit(5, controlflow.system_error_exit(
f'GAM requires Python 3.6 or newer. You are running %s.%s.%s. Please upgrade your Python version or use one of the binary GAM downloads.' % sys.version_info[ 5,
:3]) f'GAM requires Python 3.6 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(gam.ProcessGAMCommand(sys.argv)) sys.exit(gam.ProcessGAMCommand(sys.argv))
# Run from command line # Run from command line
if __name__ == "__main__": if __name__ == '__main__':
main(sys.argv) main(sys.argv)

View File

@@ -115,8 +115,7 @@ class Credentials(google.oauth2.credentials.Credentials):
Raises: Raises:
TypeError: If id_token_data is not the required dict type. TypeError: If id_token_data is not the required dict type.
""" """
super(Credentials, self).__init__( super(Credentials, self).__init__(token=token,
token=token,
refresh_token=refresh_token, refresh_token=refresh_token,
id_token=id_token, id_token=id_token,
token_uri=token_uri, token_uri=token_uri,
@@ -176,14 +175,14 @@ class Credentials(google.oauth2.credentials.Credentials):
expiry = info.get('token_expiry') expiry = info.get('token_expiry')
if expiry: if expiry:
# Convert the raw expiry to datetime # Convert the raw expiry to datetime
expiry = datetime.datetime.strptime(expiry, Credentials.DATETIME_FORMAT) expiry = datetime.datetime.strptime(expiry,
Credentials.DATETIME_FORMAT)
id_token_data = info.get('decoded_id_token') id_token_data = info.get('decoded_id_token')
# Provide backwards compatibility with field names when loading from JSON. # Provide backwards compatibility with field names when loading from JSON.
# Some field names may be different, depending on when/how the credentials # Some field names may be different, depending on when/how the credentials
# were pickled. # were pickled.
return cls( return cls(token=info.get('token', info.get('auth_token', '')),
token=info.get('token', info.get('auth_token', '')),
refresh_token=info.get('refresh_token', ''), refresh_token=info.get('refresh_token', ''),
id_token=info.get('id_token_jwt', info.get('id_token')), id_token=info.get('id_token_jwt', info.get('id_token')),
token_uri=info.get('token_uri'), token_uri=info.get('token_uri'),
@@ -226,10 +225,12 @@ class Credentials(google.oauth2.credentials.Credentials):
InvalidCredentialsFileError: When the credentials file cannot be opened. InvalidCredentialsFileError: When the credentials file cannot be opened.
EmptyCredentialsFileError: When the provided file contains no credentials. EmptyCredentialsFileError: When the provided file contains no credentials.
""" """
file_content = fileutils.read_file( file_content = fileutils.read_file(filename,
filename, continue_on_error=True, display_errors=False) continue_on_error=True,
display_errors=False)
if file_content is None: if file_content is None:
raise InvalidCredentialsFileError(f'File {filename} could not be opened') raise InvalidCredentialsFileError(
f'File {filename} could not be opened')
info = json.loads(file_content) info = json.loads(file_content)
if not info: if not info:
raise EmptyCredentialsFileError( raise EmptyCredentialsFileError(
@@ -279,14 +280,17 @@ class Credentials(google.oauth2.credentials.Credentials):
'installed': { 'installed': {
'client_id': client_id, 'client_id': client_id,
'client_secret': client_secret, 'client_secret': client_secret,
'redirect_uris': ['http://localhost', 'urn:ietf:wg:oauth:2.0:oob'], 'redirect_uris': [
'http://localhost', 'urn:ietf:wg:oauth:2.0:oob'
],
'auth_uri': 'https://accounts.google.com/o/oauth2/v2/auth', 'auth_uri': 'https://accounts.google.com/o/oauth2/v2/auth',
'token_uri': 'https://oauth2.googleapis.com/token', 'token_uri': 'https://oauth2.googleapis.com/token',
} }
} }
flow = _ShortURLFlow.from_client_config( flow = _ShortURLFlow.from_client_config(client_config,
client_config, scopes, autogenerate_code_verifier=True) scopes,
autogenerate_code_verifier=True)
flow_kwargs = {'access_type': access_type} flow_kwargs = {'access_type': access_type}
if login_hint: if login_hint:
flow_kwargs['login_hint'] = login_hint flow_kwargs['login_hint'] = login_hint
@@ -295,16 +299,17 @@ class Credentials(google.oauth2.credentials.Credentials):
# messaging about `nobrowser.txt` is co-located with the logic that uses it. # messaging about `nobrowser.txt` is co-located with the logic that uses it.
if use_console_flow: if use_console_flow:
flow.run_console( flow.run_console(
authorization_prompt_message=MESSAGE_CONSOLE_AUTHORIZATION_PROMPT, authorization_prompt_message=
MESSAGE_CONSOLE_AUTHORIZATION_PROMPT,
authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE, authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE,
**flow_kwargs) **flow_kwargs)
else: else:
flow.run_local_server( flow.run_local_server(authorization_prompt_message=
authorization_prompt_message=MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT, MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT,
success_message=MESSAGE_LOCAL_SERVER_SUCCESS, success_message=MESSAGE_LOCAL_SERVER_SUCCESS,
**flow_kwargs) **flow_kwargs)
return cls.from_google_oauth2_credentials( return cls.from_google_oauth2_credentials(flow.credentials,
flow.credentials, filename=filename) filename=filename)
@classmethod @classmethod
def from_client_secrets_file(cls, def from_client_secrets_file(cls,
@@ -345,8 +350,9 @@ class Credentials(google.oauth2.credentials.Credentials):
Returns: Returns:
Credentials Credentials
""" """
cs_data = fileutils.read_file( cs_data = fileutils.read_file(client_secrets_file,
client_secrets_file, continue_on_error=True, display_errors=False) continue_on_error=True,
display_errors=False)
if not cs_data: if not cs_data:
raise InvalidClientSecretsFileError( raise InvalidClientSecretsFileError(
f'File {client_secrets_file} could not be opened') f'File {client_secrets_file} could not be opened')
@@ -355,15 +361,15 @@ class Credentials(google.oauth2.credentials.Credentials):
client_id = cs_json['installed']['client_id'] client_id = cs_json['installed']['client_id']
# Chop off .apps.googleusercontent.com suffix as it's not needed # Chop off .apps.googleusercontent.com suffix as it's not needed
# and we need to keep things short for the Auth URL. # and we need to keep things short for the Auth URL.
client_id = re.sub(r'\.apps\.googleusercontent\.com$', '', client_id) client_id = re.sub(r'\.apps\.googleusercontent\.com$', '',
client_id)
client_secret = cs_json['installed']['client_secret'] client_secret = cs_json['installed']['client_secret']
except (ValueError, IndexError, KeyError): except (ValueError, IndexError, KeyError):
raise InvalidClientSecretsFileFormatError( raise InvalidClientSecretsFileFormatError(
f'Could not extract Client ID or Client Secret from file {client_secrets_file}' f'Could not extract Client ID or Client Secret from file {client_secrets_file}'
) )
return cls.from_client_secrets( return cls.from_client_secrets(client_id,
client_id,
client_secret, client_secret,
scopes, scopes,
access_type=access_type, access_type=access_type,
@@ -380,7 +386,8 @@ class Credentials(google.oauth2.credentials.Credentials):
CredentialsError: If no id_token is present. CredentialsError: If no id_token is present.
""" """
if not self.id_token: if not self.id_token:
raise CredentialsError('Failed to fetch token data. No id_token present.') raise CredentialsError(
'Failed to fetch token data. No id_token present.')
request = transport.create_request() request = transport.create_request()
if self.expired: if self.expired:
@@ -520,6 +527,7 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
short_url = utils.shorten_url(long_url) short_url = utils.shorten_url(long_url)
return short_url, state return short_url, state
class _FileLikeThreadLock(object): class _FileLikeThreadLock(object):
"""A threading.lock which has the same interface as filelock.Filelock.""" """A threading.lock which has the same interface as filelock.Filelock."""

View File

@@ -73,7 +73,8 @@ class CredentialsTest(unittest.TestCase):
'client_secret': 'client_secret':
self.fake_client_secret, self.fake_client_secret,
'token_expiry': 'token_expiry':
self.fake_token_expiry.strftime(oauth.Credentials.DATETIME_FORMAT), self.fake_token_expiry.strftime(
oauth.Credentials.DATETIME_FORMAT),
'id_token_data': 'id_token_data':
self.fake_token_data, self.fake_token_data,
} }
@@ -89,7 +90,8 @@ class CredentialsTest(unittest.TestCase):
def test_from_authorized_user_info_missing_required_info(self): def test_from_authorized_user_info_missing_required_info(self):
info_with_missing_fields = {'token': self.fake_token} info_with_missing_fields = {'token': self.fake_token}
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
oauth.Credentials.from_authorized_user_info(info_with_missing_fields) oauth.Credentials.from_authorized_user_info(
info_with_missing_fields)
def test_from_authorized_user_info_no_expiry_in_info(self): def test_from_authorized_user_info_no_expiry_in_info(self):
info_with_no_token_expiry = self.info_with_only_required_fields.copy() info_with_no_token_expiry = self.info_with_only_required_fields.copy()
@@ -99,8 +101,7 @@ class CredentialsTest(unittest.TestCase):
self.assertIsNone(creds.expiry) self.assertIsNone(creds.expiry)
def test_init_saves_filename(self): def test_init_saves_filename(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
filename=self.fake_filename) filename=self.fake_filename)
@@ -108,21 +109,19 @@ class CredentialsTest(unittest.TestCase):
@patch.object(oauth.google.oauth2.id_token, 'verify_oauth2_token') @patch.object(oauth.google.oauth2.id_token, 'verify_oauth2_token')
def test_init_loads_decoded_id_token_data(self, mock_verify_token): def test_init_loads_decoded_id_token_data(self, mock_verify_token):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
id_token=self.fake_id_token, id_token=self.fake_id_token,
id_token_data=self.fake_token_data) id_token_data=self.fake_token_data)
self.assertEqual( self.assertEqual(self.fake_token_data.get('field'),
self.fake_token_data.get('field'), creds.get_token_value('field')) creds.get_token_value('field'))
# Verify the fetching method was not called, since the token # Verify the fetching method was not called, since the token
# data was supposed to be loaded from the passed in info. # data was supposed to be loaded from the passed in info.
self.assertEqual(mock_verify_token.call_count, 0) self.assertEqual(mock_verify_token.call_count, 0)
def test_credentials_uses_file_lock_when_filename_provided(self): def test_credentials_uses_file_lock_when_filename_provided(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
filename=self.fake_filename) filename=self.fake_filename)
@@ -130,8 +129,7 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual(creds._lock.lock_file, '%s.lock' % creds.filename) self.assertEqual(creds._lock.lock_file, '%s.lock' % creds.filename)
def test_credentials_uses_thread_lock_when_filename_not_provided(self): def test_credentials_uses_thread_lock_when_filename_not_provided(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
filename=None) filename=None)
@@ -201,8 +199,7 @@ class CredentialsTest(unittest.TestCase):
id_token=self.fake_id_token) id_token=self.fake_id_token)
mock_flow.return_value.credentials = flow_creds mock_flow.return_value.credentials = flow_creds
creds = oauth.Credentials.from_client_secrets( creds = oauth.Credentials.from_client_secrets(self.fake_client_id,
self.fake_client_id,
self.fake_client_secret, self.fake_client_secret,
self.fake_scopes, self.fake_scopes,
use_console_flow=True) use_console_flow=True)
@@ -224,8 +221,7 @@ class CredentialsTest(unittest.TestCase):
id_token=self.fake_id_token) id_token=self.fake_id_token)
mock_flow.return_value.credentials = flow_creds mock_flow.return_value.credentials = flow_creds
creds = oauth.Credentials.from_client_secrets( creds = oauth.Credentials.from_client_secrets(self.fake_client_id,
self.fake_client_id,
self.fake_client_secret, self.fake_client_secret,
self.fake_scopes, self.fake_scopes,
use_console_flow=False) use_console_flow=False)
@@ -247,8 +243,7 @@ class CredentialsTest(unittest.TestCase):
id_token=self.fake_id_token) id_token=self.fake_id_token)
mock_flow.return_value.credentials = flow_creds mock_flow.return_value.credentials = flow_creds
oauth.Credentials.from_client_secrets( oauth.Credentials.from_client_secrets(self.fake_client_id,
self.fake_client_id,
self.fake_client_secret, self.fake_client_secret,
self.fake_scopes, self.fake_scopes,
login_hint='someone@domain.com') login_hint='someone@domain.com')
@@ -257,7 +252,8 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual('someone@domain.com', run_flow_args.get('login_hint')) self.assertEqual('someone@domain.com', run_flow_args.get('login_hint'))
def test_from_client_secrets_uses_shortened_url_flow(self): def test_from_client_secrets_uses_shortened_url_flow(self):
with patch.object(oauth._ShortURLFlow, 'from_client_config') as mock_flow: with patch.object(oauth._ShortURLFlow,
'from_client_config') as mock_flow:
flow_creds = google.oauth2.credentials.Credentials( flow_creds = google.oauth2.credentials.Credentials(
token=self.fake_token, token=self.fake_token,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
@@ -287,7 +283,8 @@ class CredentialsTest(unittest.TestCase):
filename=self.fake_filename) filename=self.fake_filename)
self.assertEqual(os.path.abspath(self.fake_filename), creds.filename) self.assertEqual(os.path.abspath(self.fake_filename), creds.filename)
def test_from_client_secrets_file_corrupt_or_missing_file_raises_error(self): def test_from_client_secrets_file_corrupt_or_missing_file_raises_error(
self):
self.assertFalse(os.path.exists(self.fake_filename)) self.assertFalse(os.path.exists(self.fake_filename))
with self.assertRaises(oauth.InvalidClientSecretsFileError): with self.assertRaises(oauth.InvalidClientSecretsFileError):
oauth.Credentials.from_client_secrets_file(self.fake_filename, oauth.Credentials.from_client_secrets_file(self.fake_filename,
@@ -309,8 +306,10 @@ class CredentialsTest(unittest.TestCase):
self, mock_read_file, mock_creds_from_client_secrets): self, mock_read_file, mock_creds_from_client_secrets):
mock_read_file.return_value = json.dumps({ mock_read_file.return_value = json.dumps({
'installed': { 'installed': {
'client_id': self.fake_client_id + '.apps.googleusercontent.com', 'client_id':
'client_secret': self.fake_client_secret, self.fake_client_id + '.apps.googleusercontent.com',
'client_secret':
self.fake_client_secret,
} }
}) })
@@ -321,28 +320,28 @@ class CredentialsTest(unittest.TestCase):
def test_get_token_value_known_token_field(self): def test_get_token_value_known_token_field(self):
token_data = {'known-field': 'known-value'} token_data = {'known-field': 'known-value'}
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
id_token_data=token_data) id_token_data=token_data)
self.assertEqual('known-value', creds.get_token_value('known-field')) self.assertEqual('known-value', creds.get_token_value('known-field'))
def test_get_token_value_unknown_field_returns_unknown(self): def test_get_token_value_unknown_field_returns_unknown(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
id_token_data=self.fake_token_data) id_token_data=self.fake_token_data)
self.assertEqual('Unknown', creds.get_token_value('unknown-field')) self.assertEqual('Unknown', creds.get_token_value('unknown-field'))
@patch.object(oauth.google.oauth2.id_token, 'verify_oauth2_token') @patch.object(oauth.google.oauth2.id_token, 'verify_oauth2_token')
def test_get_token_value_credentials_expired(self, mock_verify_oauth2_token): def test_get_token_value_credentials_expired(self,
mock_verify_oauth2_token.return_value = {'fetched-field': 'fetched-value'} mock_verify_oauth2_token):
mock_verify_oauth2_token.return_value = {
'fetched-field': 'fetched-value'
}
time_earlier_than_now = datetime.datetime.now() - datetime.timedelta( time_earlier_than_now = datetime.datetime.now() - datetime.timedelta(
minutes=5) minutes=5)
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
expiry=time_earlier_than_now, expiry=time_earlier_than_now,
@@ -357,8 +356,7 @@ class CredentialsTest(unittest.TestCase):
self.assertTrue(creds.refresh.called) self.assertTrue(creds.refresh.called)
def test_to_json_contains_all_required_fields(self): def test_to_json_contains_all_required_fields(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
id_token=self.fake_id_token, id_token=self.fake_id_token,
id_token_data=self.fake_token_data, id_token_data=self.fake_token_data,
@@ -419,8 +417,7 @@ class CredentialsTest(unittest.TestCase):
@patch.object(oauth.google.oauth2.credentials.Credentials, 'refresh') @patch.object(oauth.google.oauth2.credentials.Credentials, 'refresh')
def test_refresh_calls_super_refresh(self, mock_super_refresh): def test_refresh_calls_super_refresh(self, mock_super_refresh):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret) client_secret=self.fake_client_secret)
@@ -431,8 +428,7 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual(request, mock_super_refresh.call_args[0][0]) self.assertEqual(request, mock_super_refresh.call_args[0][0])
def test_refresh_locks_resource_during_refresh(self): def test_refresh_locks_resource_during_refresh(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret) client_secret=self.fake_client_secret)
@@ -458,8 +454,7 @@ class CredentialsTest(unittest.TestCase):
@patch.object(oauth.fileutils, 'write_file') @patch.object(oauth.fileutils, 'write_file')
def test_refresh_writes_new_credentials_to_disk_after_refresh( def test_refresh_writes_new_credentials_to_disk_after_refresh(
self, mock_write_file, mock_super_refresh): self, mock_write_file, mock_super_refresh):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
@@ -475,14 +470,14 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual('refreshed_access_token', creds.token, self.assertEqual('refreshed_access_token', creds.token,
'Access token was not refreshed') 'Access token was not refreshed')
text_written_to_file = mock_write_file.call_args[0][1] text_written_to_file = mock_write_file.call_args[0][1]
self.assertIsNotNone(text_written_to_file, 'Nothing was written to file') self.assertIsNotNone(text_written_to_file,
'Nothing was written to file')
saved_json = json.loads(text_written_to_file) saved_json = json.loads(text_written_to_file)
self.assertEqual('refreshed_access_token', saved_json['token'], self.assertEqual('refreshed_access_token', saved_json['token'],
'Refreshed access token was not saved to disk') 'Refreshed access token was not saved to disk')
def test_write_writes_credentials_to_disk(self): def test_write_writes_credentials_to_disk(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
@@ -493,8 +488,7 @@ class CredentialsTest(unittest.TestCase):
self.assertTrue(os.path.exists(self.fake_filename)) self.assertTrue(os.path.exists(self.fake_filename))
def test_write_raises_error_when_no_credentials_file_is_set(self): def test_write_raises_error_when_no_credentials_file_is_set(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret) client_secret=self.fake_client_secret)
@@ -507,8 +501,7 @@ class CredentialsTest(unittest.TestCase):
@patch.object(oauth.fileutils, 'write_file') @patch.object(oauth.fileutils, 'write_file')
def test_write_locks_resource_during_write(self, mock_write_file, def test_write_locks_resource_during_write(self, mock_write_file,
unused_mock_super_refresh): unused_mock_super_refresh):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
@@ -527,8 +520,7 @@ class CredentialsTest(unittest.TestCase):
def test_delete_removes_credentials_file(self): def test_delete_removes_credentials_file(self):
self.assertFalse(os.path.exists(self.fake_filename)) self.assertFalse(os.path.exists(self.fake_filename))
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
@@ -543,8 +535,7 @@ class CredentialsTest(unittest.TestCase):
reason=('On Windows, Filelock deletes the lock file each time the lock ' reason=('On Windows, Filelock deletes the lock file each time the lock '
'is released. Delete does not remove it.')) 'is released. Delete does not remove it.'))
def test_delete_removes_lock_file(self): def test_delete_removes_lock_file(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret, client_secret=self.fake_client_secret,
@@ -556,8 +547,7 @@ class CredentialsTest(unittest.TestCase):
self.assertFalse(os.path.exists(lock_file)) self.assertFalse(os.path.exists(lock_file))
def test_delete_is_noop_when_not_using_filelock(self): def test_delete_is_noop_when_not_using_filelock(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=None,
token=None,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret) client_secret=self.fake_client_secret)
@@ -565,8 +555,7 @@ class CredentialsTest(unittest.TestCase):
creds.delete() # This should not raise an exception. creds.delete() # This should not raise an exception.
def test_revoke_requests_credential_revoke(self): def test_revoke_requests_credential_revoke(self):
creds = oauth.Credentials( creds = oauth.Credentials(token=self.fake_token,
token=self.fake_token,
refresh_token=self.fake_refresh_token, refresh_token=self.fake_refresh_token,
client_id=self.fake_client_id, client_id=self.fake_client_id,
client_secret=self.fake_client_secret) client_secret=self.fake_client_secret)
@@ -594,7 +583,9 @@ class ShortUrlFlowTest(unittest.TestCase):
'installed': { 'installed': {
'client_id': self.fake_client_id, 'client_id': self.fake_client_id,
'client_secret': self.fake_client_secret, 'client_secret': self.fake_client_secret,
'redirect_uris': ['http://localhost', 'urn:ietf:wg:oauth:2.0:oob'], 'redirect_uris': [
'http://localhost', 'urn:ietf:wg:oauth:2.0:oob'
],
'auth_uri': 'https://accounts.google.com/o/oauth2/v2/auth', 'auth_uri': 'https://accounts.google.com/o/oauth2/v2/auth',
'token_uri': 'https://oauth2.googleapis.com/token', 'token_uri': 'https://oauth2.googleapis.com/token',
} }
@@ -693,7 +684,8 @@ class ShortUrlFlowTest(unittest.TestCase):
mock_http = MagicMock() mock_http = MagicMock()
mock_response = MagicMock() mock_response = MagicMock()
mock_response.status = 200 mock_response.status = 200
content = json.dumps({}) # This json content contains no "short-url" key content = json.dumps(
{}) # This json content contains no "short-url" key
mock_http.request.return_value = (mock_response, content) mock_http.request.return_value = (mock_response, content)
url, state = url_flow.authorization_url(http=mock_http) url, state = url_flow.authorization_url(http=mock_http)

View File

@@ -18,7 +18,8 @@ class ControlFlowTest(unittest.TestCase):
self.assertEqual(context_manager.exception.code, 100) self.assertEqual(context_manager.exception.code, 100)
@patch.object(controlflow.display, 'print_error') @patch.object(controlflow.display, 'print_error')
def test_system_error_exit_prints_error_before_exiting(self, mock_print_err): def test_system_error_exit_prints_error_before_exiting(
self, mock_print_err):
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
controlflow.system_error_exit(100, 'exit message') controlflow.system_error_exit(100, 'exit message')
self.assertIn('exit message', mock_print_err.call_args[0][0]) self.assertIn('exit message', mock_print_err.call_args[0][0])
@@ -93,14 +94,15 @@ class ControlFlowTest(unittest.TestCase):
self.assertIn(message, mock_stderr_write.call_args[0][0]) self.assertIn(message, mock_stderr_write.call_args[0][0])
@patch.object(controlflow.time, 'sleep') @patch.object(controlflow.time, 'sleep')
def test_wait_on_failure_only_prints_after_threshold(self, unused_mock_sleep): def test_wait_on_failure_only_prints_after_threshold(
self, unused_mock_sleep):
total_attempts = 5 total_attempts = 5
threshold = 3 threshold = 3
with patch.object(controlflow.sys.stderr, 'write') as mock_stderr_write: with patch.object(controlflow.sys.stderr, 'write') as mock_stderr_write:
for attempt in range(1, total_attempts + 1): for attempt in range(1, total_attempts + 1):
controlflow.wait_on_failure( controlflow.wait_on_failure(attempt,
attempt,
total_attempts, total_attempts,
'Attempt #%s' % attempt, 'Attempt #%s' % attempt,
error_print_threshold=threshold) error_print_threshold=threshold)
self.assertEqual(total_attempts - threshold, mock_stderr_write.call_count) self.assertEqual(total_attempts - threshold,
mock_stderr_write.call_count)

View File

@@ -19,8 +19,11 @@ from gam import gapi
def current_count(i, count): def current_count(i, count):
return f' ({i}/{count})' if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else '' return f' ({i}/{count})' if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else ''
def current_count_nl(i, count): def current_count_nl(i, count):
return f' ({i}/{count})\n' if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else '\n' return f' ({i}/{count})\n' if (
count > GC_Values[GC_SHOW_COUNTS_MIN]) else '\n'
def add_field_to_fields_list(fieldName, fieldsChoiceMap, fieldsList): def add_field_to_fields_list(fieldName, fieldsChoiceMap, fieldsList):
fields = fieldsChoiceMap[fieldName.lower()] fields = fieldsChoiceMap[fieldName.lower()]
@@ -29,18 +32,21 @@ def add_field_to_fields_list(fieldName, fieldsChoiceMap, fieldsList):
else: else:
fieldsList.append(fields) fieldsList.append(fields)
# Write a CSV file # Write a CSV file
def add_titles_to_csv_file(addTitles, titles): def add_titles_to_csv_file(addTitles, titles):
for title in addTitles: for title in addTitles:
if title not in titles: if title not in titles:
titles.append(title) titles.append(title)
def add_row_titles_to_csv_file(row, csvRows, titles): def add_row_titles_to_csv_file(row, csvRows, titles):
csvRows.append(row) csvRows.append(row)
for title in row: for title in row:
if title not in titles: if title not in titles:
titles.append(title) titles.append(title)
# fieldName is command line argument # fieldName is command line argument
# fieldNameMap maps fieldName to API field names; CSV file header will be API field name # fieldNameMap maps fieldName to API field names; CSV file header will be API field name
#ARGUMENT_TO_PROPERTY_MAP = { #ARGUMENT_TO_PROPERTY_MAP = {
@@ -49,13 +55,15 @@ def add_row_titles_to_csv_file(row, csvRows, titles):
# } # }
# fieldsList is the list of API fields # fieldsList is the list of API fields
# fieldsTitles maps the API field name to the CSV file header # fieldsTitles maps the API field name to the CSV file header
def add_field_to_csv_file(fieldName, fieldNameMap, fieldsList, fieldsTitles, titles): def add_field_to_csv_file(fieldName, fieldNameMap, fieldsList, fieldsTitles,
titles):
for ftList in fieldNameMap[fieldName]: for ftList in fieldNameMap[fieldName]:
if ftList not in fieldsTitles: if ftList not in fieldsTitles:
fieldsList.append(ftList) fieldsList.append(ftList)
fieldsTitles[ftList] = ftList fieldsTitles[ftList] = ftList
add_titles_to_csv_file([ftList], titles) add_titles_to_csv_file([ftList], titles)
# fieldName is command line argument # fieldName is command line argument
# fieldNameTitleMap maps fieldName to API field name and CSV file header # fieldNameTitleMap maps fieldName to API field name and CSV file header
#ARGUMENT_TO_PROPERTY_TITLE_MAP = { #ARGUMENT_TO_PROPERTY_TITLE_MAP = {
@@ -64,13 +72,15 @@ def add_field_to_csv_file(fieldName, fieldNameMap, fieldsList, fieldsTitles, tit
# } # }
# fieldsList is the list of API fields # fieldsList is the list of API fields
# fieldsTitles maps the API field name to the CSV file header # fieldsTitles maps the API field name to the CSV file header
def add_field_title_to_csv_file(fieldName, fieldNameTitleMap, fieldsList, fieldsTitles, titles): def add_field_title_to_csv_file(fieldName, fieldNameTitleMap, fieldsList,
fieldsTitles, titles):
ftList = fieldNameTitleMap[fieldName] ftList = fieldNameTitleMap[fieldName]
for i in range(0, len(ftList), 2): for i in range(0, len(ftList), 2):
if ftList[i] not in fieldsTitles: if ftList[i] not in fieldsTitles:
fieldsList.append(ftList[i]) fieldsList.append(ftList[i])
fieldsTitles[ftList[i]] = ftList[i+1] fieldsTitles[ftList[i]] = ftList[i + 1]
add_titles_to_csv_file([ftList[i+1]], titles) add_titles_to_csv_file([ftList[i + 1]], titles)
def sort_csv_titles(firstTitle, titles): def sort_csv_titles(firstTitle, titles):
restoreTitles = [] restoreTitles = []
@@ -82,17 +92,24 @@ def sort_csv_titles(firstTitle, titles):
for title in restoreTitles[::-1]: for title in restoreTitles[::-1]:
titles.insert(0, title) titles.insert(0, title)
def QuotedArgumentList(items): def QuotedArgumentList(items):
return ' '.join([item if item and (item.find(' ') == -1) and (item.find(',') == -1) else '"'+item+'"' for item in items]) return ' '.join([
item if item and (item.find(' ') == -1) and
(item.find(',') == -1) else '"' + item + '"' for item in items
])
def write_csv_file(csvRows, titles, list_type, todrive): def write_csv_file(csvRows, titles, list_type, todrive):
def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate): def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate):
if not rowDate or not isinstance(rowDate, str): if not rowDate or not isinstance(rowDate, str):
return False return False
try: try:
rowTime = dateutil.parser.parse(rowDate, ignoretz=True) rowTime = dateutil.parser.parse(rowDate, ignoretz=True)
if dateMode: if dateMode:
rowDate = datetime.datetime(rowTime.year, rowTime.month, rowTime.day).isoformat()+'Z' rowDate = datetime.datetime(rowTime.year, rowTime.month,
rowTime.day).isoformat() + 'Z'
except ValueError: except ValueError:
rowDate = NEVER_TIME rowDate = NEVER_TIME
if op == '<': if op == '<':
@@ -125,6 +142,7 @@ def write_csv_file(csvRows, titles, list_type, todrive):
if op == '!=': if op == '!=':
return rowCount != filterCount return rowCount != filterCount
return rowCount == filterCount return rowCount == filterCount
def rowBooleanFilterMatch(rowBoolean, filterBoolean): def rowBooleanFilterMatch(rowBoolean, filterBoolean):
if not isinstance(rowBoolean, bool): if not isinstance(rowBoolean, bool):
return False return False
@@ -139,32 +157,63 @@ def write_csv_file(csvRows, titles, list_type, todrive):
if GC_Values[GC_CSV_ROW_FILTER]: if GC_Values[GC_CSV_ROW_FILTER]:
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()): for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
if column not in titles: if column not in titles:
sys.stderr.write(f'WARNING: Row filter column "{column}" is not in output columns\n') sys.stderr.write(
f'WARNING: Row filter column "{column}" is not in output columns\n'
)
continue continue
if filterVal[0] == 'regex': if filterVal[0] == 'regex':
csvRows = [row for row in csvRows if filterVal[1].search(str(row.get(column, '')))] csvRows = [
row for row in csvRows
if filterVal[1].search(str(row.get(column, '')))
]
elif filterVal[0] == 'notregex': elif filterVal[0] == 'notregex':
csvRows = [row for row in csvRows if not filterVal[1].search(str(row.get(column, '')))] csvRows = [
row for row in csvRows
if not filterVal[1].search(str(row.get(column, '')))
]
elif filterVal[0] in ['date', 'time']: elif filterVal[0] in ['date', 'time']:
csvRows = [row for row in csvRows if rowDateTimeFilterMatch(filterVal[0] == 'date', row.get(column, ''), filterVal[1], filterVal[2])] csvRows = [
row for row in csvRows if rowDateTimeFilterMatch(
filterVal[0] == 'date', row.get(column, ''),
filterVal[1], filterVal[2])
]
elif filterVal[0] == 'count': elif filterVal[0] == 'count':
csvRows = [row for row in csvRows if rowCountFilterMatch(row.get(column, 0), filterVal[1], filterVal[2])] csvRows = [
row for row in csvRows if rowCountFilterMatch(
row.get(column, 0), filterVal[1], filterVal[2])
]
else: #boolean else: #boolean
csvRows = [row for row in csvRows if rowBooleanFilterMatch(row.get(column, False), filterVal[1])] csvRows = [
row for row in csvRows if rowBooleanFilterMatch(
row.get(column, False), filterVal[1])
]
if GC_Values[GC_CSV_HEADER_FILTER] or GC_Values[GC_CSV_HEADER_DROP_FILTER]: if GC_Values[GC_CSV_HEADER_FILTER] or GC_Values[GC_CSV_HEADER_DROP_FILTER]:
if GC_Values[GC_CSV_HEADER_DROP_FILTER]: if GC_Values[GC_CSV_HEADER_DROP_FILTER]:
titles = [t for t in titles if not headerFilterMatch(GC_Values[GC_CSV_HEADER_DROP_FILTER], t)] titles = [
t for t in titles if
not headerFilterMatch(GC_Values[GC_CSV_HEADER_DROP_FILTER], t)
]
if GC_Values[GC_CSV_HEADER_FILTER]: if GC_Values[GC_CSV_HEADER_FILTER]:
titles = [t for t in titles if headerFilterMatch(GC_Values[GC_CSV_HEADER_FILTER], t)] titles = [
t for t in titles
if headerFilterMatch(GC_Values[GC_CSV_HEADER_FILTER], t)
]
if not titles: if not titles:
controlflow.system_error_exit(3, 'No columns selected with GAM_CSV_HEADER_FILTER and GAM_CSV_HEADER_DROP_FILTER\n') controlflow.system_error_exit(
3,
'No columns selected with GAM_CSV_HEADER_FILTER and GAM_CSV_HEADER_DROP_FILTER\n'
)
return return
csv.register_dialect('nixstdout', lineterminator='\n') csv.register_dialect('nixstdout', lineterminator='\n')
if todrive: if todrive:
write_to = io.StringIO() write_to = io.StringIO()
else: else:
write_to = sys.stdout write_to = sys.stdout
writer = csv.DictWriter(write_to, fieldnames=titles, dialect='nixstdout', extrasaction='ignore', quoting=csv.QUOTE_MINIMAL) writer = csv.DictWriter(write_to,
fieldnames=titles,
dialect='nixstdout',
extrasaction='ignore',
quoting=csv.QUOTE_MINIMAL)
try: try:
writer.writerow(dict((item, item) for item in writer.fieldnames)) writer.writerow(dict((item, item) for item in writer.fieldnames))
writer.writerows(csvRows) writer.writerows(csvRows)
@@ -185,16 +234,23 @@ and follow recommend steps to authorize GAM for Drive access.''')
data_size = len(write_to.getvalue()) data_size = len(write_to.getvalue())
max_sheet_bytes = int(result['maxImportSizes'][MIMETYPE_GA_SPREADSHEET]) max_sheet_bytes = int(result['maxImportSizes'][MIMETYPE_GA_SPREADSHEET])
if cell_count > MAX_GOOGLE_SHEET_CELLS or data_size > max_sheet_bytes: if cell_count > MAX_GOOGLE_SHEET_CELLS or data_size > max_sheet_bytes:
print(f'{WARNING_PREFIX}{MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET}') print(
f'{WARNING_PREFIX}{MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET}'
)
mimeType = 'text/csv' mimeType = 'text/csv'
else: else:
mimeType = MIMETYPE_GA_SPREADSHEET mimeType = MIMETYPE_GA_SPREADSHEET
body = {'description': QuotedArgumentList(sys.argv), body = {
'description': QuotedArgumentList(sys.argv),
'name': f'{GC_Values[GC_DOMAIN]} - {list_type}', 'name': f'{GC_Values[GC_DOMAIN]} - {list_type}',
'mimeType': mimeType} 'mimeType': mimeType
result = gapi.call(drive.files(), 'create', fields='webViewLink', }
result = gapi.call(drive.files(),
'create',
fields='webViewLink',
body=body, body=body,
media_body=googleapiclient.http.MediaInMemoryUpload(write_to.getvalue().encode(), media_body=googleapiclient.http.MediaInMemoryUpload(
write_to.getvalue().encode(),
mimetype='text/csv')) mimetype='text/csv'))
file_url = result['webViewLink'] file_url = result['webViewLink']
if GC_Values[GC_NO_BROWSER]: if GC_Values[GC_NO_BROWSER]:
@@ -205,6 +261,7 @@ and follow recommend steps to authorize GAM for Drive access.''')
else: else:
webbrowser.open(file_url) webbrowser.open(file_url)
def print_error(message): def print_error(message):
"""Prints a one-line error message to stderr in a standard format.""" """Prints a one-line error message to stderr in a standard format."""
sys.stderr.write('\n{0}{1}\n'.format(ERROR_PREFIX, message)) sys.stderr.write('\n{0}{1}\n'.format(ERROR_PREFIX, message))
@@ -214,10 +271,12 @@ def print_warning(message):
"""Prints a one-line warning message to stderr in a standard format.""" """Prints a one-line warning message to stderr in a standard format."""
sys.stderr.write('\n{0}{1}\n'.format(WARNING_PREFIX, message)) sys.stderr.write('\n{0}{1}\n'.format(WARNING_PREFIX, message))
def print_json(object_value, spacing=''): def print_json(object_value, spacing=''):
"""Prints Dict or Array to screen in clean human-readable format..""" """Prints Dict or Array to screen in clean human-readable format.."""
if isinstance(object_value, list): if isinstance(object_value, list):
if len(object_value) == 1 and isinstance(object_value[0], (str, int, bool)): if len(object_value) == 1 and isinstance(object_value[0],
(str, int, bool)):
sys.stdout.write(f'{object_value[0]}\n') sys.stdout.write(f'{object_value[0]}\n')
return return
if spacing: if spacing:

View File

@@ -21,8 +21,10 @@ def _open_file(filename, mode, encoding=None, newline=None):
elif 'r' in mode and encoding.lower().replace('-', '') == 'utf8': elif 'r' in mode and encoding.lower().replace('-', '') == 'utf8':
encoding = UTF8_SIG encoding = UTF8_SIG
return open( return open(os.path.expanduser(filename),
os.path.expanduser(filename), mode, newline=newline, encoding=encoding) mode,
newline=newline,
encoding=encoding)
def open_file(filename, def open_file(filename,
@@ -67,7 +69,8 @@ def open_file(filename,
utf8_bom_bytes = utf_bom.encode('UTF-8') utf8_bom_bytes = utf_bom.encode('UTF-8')
iso_8859_1_bom = utf8_bom_bytes.decode('iso-8859-1').encode( iso_8859_1_bom = utf8_bom_bytes.decode('iso-8859-1').encode(
'iso-8859-1') 'iso-8859-1')
has_bom = f.read(3).encode('iso-8859-1', 'replace') == iso_8859_1_bom has_bom = f.read(3).encode('iso-8859-1',
'replace') == iso_8859_1_bom
else: else:
has_bom = f.read(1) == utf_bom has_bom = f.read(1) == utf_bom
@@ -133,7 +136,8 @@ def read_file(filename,
# Read from stdin, rather than a file. # Read from stdin, rather than a file.
return str(sys.stdin.read()) return str(sys.stdin.read())
with _open_file(filename, mode, newline=newline, encoding=encoding) as f: with _open_file(filename, mode, newline=newline,
encoding=encoding) as f:
return f.read() return f.read()
except IOError as e: except IOError as e:

View File

@@ -93,7 +93,9 @@ class FileutilsTest(unittest.TestCase):
fake_file = io.BytesIO(bom_prefixed_data) fake_file = io.BytesIO(bom_prefixed_data)
mock_open = MagicMock(spec=open, return_value=fake_file) mock_open = MagicMock(spec=open, return_value=fake_file)
with patch.object(fileutils, 'open', mock_open): with patch.object(fileutils, 'open', mock_open):
f = fileutils.open_file(self.fake_path, mode='rb', strip_utf_bom=True) f = fileutils.open_file(self.fake_path,
mode='rb',
strip_utf_bom=True)
self.assertEqual(b'foobar', f.read()) self.assertEqual(b'foobar', f.read())
def test_open_file_strip_utf_bom_when_no_bom_in_data(self): def test_open_file_strip_utf_bom_when_no_bom_in_data(self):
@@ -145,17 +147,20 @@ class FileutilsTest(unittest.TestCase):
def test_read_file_continues_on_errors_without_displaying( def test_read_file_continues_on_errors_without_displaying(
self, mock_open_file, mock_print_warning): self, mock_open_file, mock_print_warning):
mock_open_file.side_effect = IOError() mock_open_file.side_effect = IOError()
contents = fileutils.read_file( contents = fileutils.read_file(self.fake_path,
self.fake_path, continue_on_error=True, display_errors=False) continue_on_error=True,
display_errors=False)
self.assertIsNone(contents) self.assertIsNone(contents)
self.assertFalse(mock_print_warning.called) self.assertFalse(mock_print_warning.called)
@patch.object(fileutils.display, 'print_warning') @patch.object(fileutils.display, 'print_warning')
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
def test_read_file_displays_errors(self, mock_open_file, mock_print_warning): def test_read_file_displays_errors(self, mock_open_file,
mock_print_warning):
mock_open_file.side_effect = IOError() mock_open_file.side_effect = IOError()
fileutils.read_file( fileutils.read_file(self.fake_path,
self.fake_path, continue_on_error=True, display_errors=True) continue_on_error=True,
display_errors=True)
self.assertTrue(mock_print_warning.called) self.assertTrue(mock_print_warning.called)
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
@@ -175,16 +180,18 @@ class FileutilsTest(unittest.TestCase):
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
def test_read_file_exits_code_2_on_unicodeerror(self, mock_open_file): def test_read_file_exits_code_2_on_unicodeerror(self, mock_open_file):
mock_open_file.return_value.__enter__().read.side_effect = UnicodeError() mock_open_file.return_value.__enter__().read.side_effect = UnicodeError(
)
with self.assertRaises(SystemExit) as context: with self.assertRaises(SystemExit) as context:
fileutils.read_file(self.fake_path) fileutils.read_file(self.fake_path)
self.assertEqual(context.exception.code, 2) self.assertEqual(context.exception.code, 2)
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
def test_read_file_exits_code_2_on_unicodedecodeerror(self, mock_open_file): def test_read_file_exits_code_2_on_unicodedecodeerror(self, mock_open_file):
fake_decode_error = UnicodeDecodeError('fake-encoding', b'fakebytes', 0, 1, fake_decode_error = UnicodeDecodeError('fake-encoding', b'fakebytes', 0,
'testing only') 1, 'testing only')
mock_open_file.return_value.__enter__().read.side_effect = fake_decode_error mock_open_file.return_value.__enter__(
).read.side_effect = fake_decode_error
with self.assertRaises(SystemExit) as context: with self.assertRaises(SystemExit) as context:
fileutils.read_file(self.fake_path) fileutils.read_file(self.fake_path)
self.assertEqual(context.exception.code, 2) self.assertEqual(context.exception.code, 2)
@@ -205,8 +212,7 @@ class FileutilsTest(unittest.TestCase):
def test_write_file_continues_on_errors_without_displaying( def test_write_file_continues_on_errors_without_displaying(
self, mock_open_file, mock_print_error): self, mock_open_file, mock_print_error):
mock_open_file.side_effect = IOError() mock_open_file.side_effect = IOError()
status = fileutils.write_file( status = fileutils.write_file(self.fake_path,
self.fake_path,
'foo data', 'foo data',
continue_on_error=True, continue_on_error=True,
display_errors=False) display_errors=False)
@@ -217,8 +223,10 @@ class FileutilsTest(unittest.TestCase):
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
def test_write_file_displays_errors(self, mock_open_file, mock_print_error): def test_write_file_displays_errors(self, mock_open_file, mock_print_error):
mock_open_file.side_effect = IOError() mock_open_file.side_effect = IOError()
fileutils.write_file( fileutils.write_file(self.fake_path,
self.fake_path, 'foo data', continue_on_error=True, display_errors=True) 'foo data',
continue_on_error=True,
display_errors=True)
self.assertTrue(mock_print_error.called) self.assertTrue(mock_print_error.called)
@patch.object(fileutils, '_open_file') @patch.object(fileutils, '_open_file')
@@ -226,7 +234,9 @@ class FileutilsTest(unittest.TestCase):
self, mock_open_file): self, mock_open_file):
mock_open_file.side_effect = IOError() mock_open_file.side_effect = IOError()
with self.assertRaises(SystemExit) as context: with self.assertRaises(SystemExit) as context:
fileutils.write_file(self.fake_path, 'foo data', continue_on_error=False) fileutils.write_file(self.fake_path,
'foo data',
continue_on_error=False)
self.assertEqual(context.exception.code, 6) self.assertEqual(context.exception.code, 6)

View File

@@ -68,18 +68,24 @@ def call(service,
if http_status == 0: if http_status == 0:
return None return None
is_known_error_reason = reason in [r.value for r in errors.ErrorReason] is_known_error_reason = reason in [
if is_known_error_reason and errors.ErrorReason(reason) in throw_reasons: r.value for r in errors.ErrorReason
if errors.ErrorReason(reason) in errors.ERROR_REASON_TO_EXCEPTION: ]
raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason(reason)]( if is_known_error_reason and errors.ErrorReason(
message) reason) in throw_reasons:
if errors.ErrorReason(
reason) in errors.ERROR_REASON_TO_EXCEPTION:
raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason(
reason)](message)
raise e raise e
if (n != retries) and (is_known_error_reason and errors.ErrorReason( if (n != retries) and (is_known_error_reason and errors.ErrorReason(
reason) in errors.DEFAULT_RETRY_REASONS + retry_reasons): reason) in errors.DEFAULT_RETRY_REASONS + retry_reasons):
controlflow.wait_on_failure(n, retries, reason) controlflow.wait_on_failure(n, retries, reason)
continue continue
if soft_errors: if soft_errors:
display.print_error(f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}') display.print_error(
f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}'
)
return None return None
controlflow.system_error_exit( controlflow.system_error_exit(
int(http_status), f'{http_status}: {message} - {reason}') int(http_status), f'{http_status}: {message} - {reason}')
@@ -89,10 +95,12 @@ def call(service,
errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons) errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons)
if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons: if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons:
raise errors.GapiServiceNotAvailableError(str(e)) raise errors.GapiServiceNotAvailableError(str(e))
display.print_error(f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}') display.print_error(
f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}')
return None return None
except ValueError as e: except ValueError as e:
if hasattr(service._http, 'cache') and service._http.cache is not None: if hasattr(service._http,
'cache') and service._http.cache is not None:
service._http.cache = None service._http.cache = None
continue continue
controlflow.system_error_exit(4, str(e)) controlflow.system_error_exit(4, str(e))
@@ -130,8 +138,7 @@ def get_items(service,
Returns: Returns:
The list of items in the first page of a response. The list of items in the first page of a response.
""" """
results = call( results = call(service,
service,
function, function,
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
retry_reasons=retry_reasons, retry_reasons=retry_reasons,
@@ -159,7 +166,8 @@ def _get_max_page_size_for_api_call(service, function, **kwargs):
for a_method in resource.get('methods', {}).values(): for a_method in resource.get('methods', {}).values():
if a_method.get('id') == api_id: if a_method.get('id') == api_id:
if not a_method.get('parameters') or a_method['parameters'].get( if not a_method.get('parameters') or a_method['parameters'].get(
'pageSize') or not a_method['parameters'].get('maxResults'): 'pageSize'
) or not a_method['parameters'].get('maxResults'):
# Make sure API call supports maxResults. For now we don't care to # Make sure API call supports maxResults. For now we don't care to
# set pageSize since all known pageSize API calls have # set pageSize since all known pageSize API calls have
# default pageSize == max pageSize. # default pageSize == max pageSize.
@@ -176,6 +184,7 @@ TOTAL_ITEMS_MARKER = '%%total_items%%'
FIRST_ITEM_MARKER = '%%first_item%%' FIRST_ITEM_MARKER = '%%first_item%%'
LAST_ITEM_MARKER = '%%last_item%%' LAST_ITEM_MARKER = '%%last_item%%'
def got_total_items_msg(items, eol): def got_total_items_msg(items, eol):
"""Format a page_message to be used by get_all_pages """Format a page_message to be used by get_all_pages
@@ -192,6 +201,7 @@ def got_total_items_msg(items, eol):
return f'Got {TOTAL_ITEMS_MARKER} {items}{eol}' return f'Got {TOTAL_ITEMS_MARKER} {items}{eol}'
def got_total_items_first_last_msg(items): def got_total_items_first_last_msg(items):
"""Format a page_message to be used by get_all_pages """Format a page_message to be used by get_all_pages
@@ -205,7 +215,8 @@ def got_total_items_first_last_msg(items):
The formatted page_message The formatted page_message
""" """
return f'Got {TOTAL_ITEMS_MARKER} {items}: {FIRST_ITEM_MARKER} - {LAST_ITEM_MARKER}'+'\n' return f'Got {TOTAL_ITEMS_MARKER} {items}: {FIRST_ITEM_MARKER} - {LAST_ITEM_MARKER}' + '\n'
def get_all_pages(service, def get_all_pages(service,
function, function,
@@ -258,8 +269,7 @@ def get_all_pages(service,
page_token = None page_token = None
total_items = 0 total_items = 0
while True: while True:
page = call( page = call(service,
service,
function, function,
soft_errors=soft_errors, soft_errors=soft_errors,
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
@@ -278,12 +288,16 @@ def get_all_pages(service,
# Show a paging message to the user that indicates paging progress # Show a paging message to the user that indicates paging progress
if page_message: if page_message:
show_message = page_message.replace(TOTAL_ITEMS_MARKER, str(total_items)) show_message = page_message.replace(TOTAL_ITEMS_MARKER,
str(total_items))
if message_attribute: if message_attribute:
first_item = page_items[0] if num_page_items > 0 else {} first_item = page_items[0] if num_page_items > 0 else {}
last_item = page_items[-1] if num_page_items > 1 else first_item last_item = page_items[-1] if num_page_items > 1 else first_item
show_message = show_message.replace(FIRST_ITEM_MARKER, str(first_item.get(message_attribute, ''))) show_message = show_message.replace(
show_message = show_message.replace(LAST_ITEM_MARKER, str(last_item.get(message_attribute, ''))) FIRST_ITEM_MARKER,
str(first_item.get(message_attribute, '')))
show_message = show_message.replace(
LAST_ITEM_MARKER, str(last_item.get(message_attribute, '')))
sys.stderr.write('\r') sys.stderr.write('\r')
sys.stderr.flush() sys.stderr.flush()
sys.stderr.write(show_message) sys.stderr.write(show_message)
@@ -324,5 +338,6 @@ def handle_oauth_token_error(e, soft_errors):
GM_Globals[GM_CURRENT_API_USER])) GM_Globals[GM_CURRENT_API_USER]))
controlflow.system_error_exit(18, f'Authentication Token Error - {str(e)}') controlflow.system_error_exit(18, f'Authentication Token Error - {str(e)}')
def get_enum_values_minus_unspecified(values): def get_enum_values_minus_unspecified(values):
return [a_type for a_type in values if '_UNSPECIFIED' not in a_type] return [a_type for a_type in values if '_UNSPECIFIED' not in a_type]

View File

@@ -86,8 +86,10 @@ class GapiTest(unittest.TestCase):
self.assertEqual(response, self.mock_method().execute.return_value) self.assertEqual(response, self.mock_method().execute.return_value)
def test_call_passes_target_method_params(self): def test_call_passes_target_method_params(self):
gapi.call( gapi.call(self.mock_service,
self.mock_service, self.mock_method_name, my_param_1=1, my_param_2=2) self.mock_method_name,
my_param_1=1,
my_param_2=2)
self.assertEqual(self.mock_method.call_count, 1) self.assertEqual(self.mock_method.call_count, 1)
method_kwargs = self.mock_method.call_args[1] method_kwargs = self.mock_method.call_args[1]
self.assertEqual(method_kwargs.get('my_param_1'), 1) self.assertEqual(method_kwargs.get('my_param_1'), 1)
@@ -104,11 +106,12 @@ class GapiTest(unittest.TestCase):
fake_http_error, fake_200_response fake_http_error, fake_200_response
] ]
response = gapi.call( response = gapi.call(self.mock_service,
self.mock_service, self.mock_method_name, soft_errors=True) self.mock_method_name,
soft_errors=True)
self.assertEqual(response, fake_200_response) self.assertEqual(response, fake_200_response)
self.assertEqual( self.assertEqual(self.mock_service._http.credentials.refresh.call_count,
self.mock_service._http.credentials.refresh.call_count, 1) 1)
self.assertEqual(self.mock_method.return_value.execute.call_count, 2) self.assertEqual(self.mock_method.return_value.execute.call_count, 2)
def test_call_throws_for_provided_reason(self): def test_call_throws_for_provided_reason(self):
@@ -118,8 +121,7 @@ class GapiTest(unittest.TestCase):
gam_exception = errors.ERROR_REASON_TO_EXCEPTION[throw_reason] gam_exception = errors.ERROR_REASON_TO_EXCEPTION[throw_reason]
with self.assertRaises(gam_exception): with self.assertRaises(gam_exception):
gapi.call( gapi.call(self.mock_service,
self.mock_service,
self.mock_method_name, self.mock_method_name,
throw_reasons=[throw_reason]) throw_reasons=[throw_reason])
@@ -133,15 +135,17 @@ class GapiTest(unittest.TestCase):
default_throw_reason = errors.ErrorReason.BACKEND_ERROR default_throw_reason = errors.ErrorReason.BACKEND_ERROR
self.assertIn(default_throw_reason, errors.DEFAULT_RETRY_REASONS) self.assertIn(default_throw_reason, errors.DEFAULT_RETRY_REASONS)
fake_http_error = create_http_error(404, default_throw_reason, 'message') fake_http_error = create_http_error(404, default_throw_reason,
'message')
fake_200_response = MagicMock() fake_200_response = MagicMock()
# Fail once, then succeed on retry # Fail once, then succeed on retry
self.mock_method.return_value.execute.side_effect = [ self.mock_method.return_value.execute.side_effect = [
fake_http_error, fake_200_response fake_http_error, fake_200_response
] ]
response = gapi.call( response = gapi.call(self.mock_service,
self.mock_service, self.mock_method_name, retry_reasons=[]) self.mock_method_name,
retry_reasons=[])
self.assertEqual(response, fake_200_response) self.assertEqual(response, fake_200_response)
self.assertEqual(self.mock_method.return_value.execute.call_count, 2) self.assertEqual(self.mock_method.return_value.execute.call_count, 2)
# Make sure a backoff technique was used for retry. # Make sure a backoff technique was used for retry.
@@ -165,14 +169,14 @@ class GapiTest(unittest.TestCase):
'This error should not cause the request to be retried') 'This error should not cause the request to be retried')
# Fail once, then succeed on retry # Fail once, then succeed on retry
self.mock_method.return_value.execute.side_effect = [ self.mock_method.return_value.execute.side_effect = [
fake_retrieable_error1, fake_retrieable_error2, fake_non_retriable_error fake_retrieable_error1, fake_retrieable_error2,
fake_non_retriable_error
] ]
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
# The third call should raise the SystemExit when non_retriable_error is # The third call should raise the SystemExit when non_retriable_error is
# raised. # raised.
gapi.call( gapi.call(self.mock_service,
self.mock_service,
self.mock_method_name, self.mock_method_name,
retry_reasons=[retry_reason1, retry_reason2]) retry_reasons=[retry_reason1, retry_reason2])
@@ -231,7 +235,8 @@ class GapiTest(unittest.TestCase):
response = gapi.call(self.mock_service, self.mock_method_name) response = gapi.call(self.mock_service, self.mock_method_name)
self.assertEqual(response, fake_200_response) self.assertEqual(response, fake_200_response)
# HTTP cached connections should be cleared on receiving this error # HTTP cached connections should be cleared on receiving this error
self.assertNotEqual(http_connections, self.mock_service._http.connections) self.assertNotEqual(http_connections,
self.mock_service._http.connections)
self.assertEqual(self.mock_method.return_value.execute.call_count, 2) self.assertEqual(self.mock_method.return_value.execute.call_count, 2)
# Make sure a backoff technique was used for retry. # Make sure a backoff technique was used for retry.
self.assertEqual(mock_wait_on_failure.call_count, 1) self.assertEqual(mock_wait_on_failure.call_count, 1)
@@ -250,13 +255,16 @@ class GapiTest(unittest.TestCase):
field_name = 'things' field_name = 'things'
fake_response = {field_name: [{}, {}, {}]} fake_response = {field_name: [{}, {}, {}]}
self.mock_method.return_value.execute.return_value = fake_response self.mock_method.return_value.execute.return_value = fake_response
page = gapi.get_items( page = gapi.get_items(self.mock_service,
self.mock_service, self.mock_method_name, items=field_name) self.mock_method_name,
items=field_name)
self.assertEqual(page, fake_response[field_name]) self.assertEqual(page, fake_response[field_name])
def test_get_items_passes_additional_kwargs_to_service(self): def test_get_items_passes_additional_kwargs_to_service(self):
gapi.get_items( gapi.get_items(self.mock_service,
self.mock_service, self.mock_method_name, my_param_1=1, my_param_2=2) self.mock_method_name,
my_param_1=1,
my_param_2=2)
self.assertEqual(self.mock_method.call_count, 1) self.assertEqual(self.mock_method.call_count, 1)
method_kwargs = self.mock_method.call_args[1] method_kwargs = self.mock_method.call_args[1]
self.assertEqual(1, method_kwargs.get('my_param_1')) self.assertEqual(1, method_kwargs.get('my_param_1'))
@@ -273,18 +281,22 @@ class GapiTest(unittest.TestCase):
page_1 = {'items': ['1-1', '1-2', '1-3'], 'nextPageToken': '2'} page_1 = {'items': ['1-1', '1-2', '1-3'], 'nextPageToken': '2'}
page_2 = {'items': ['2-1', '2-2', '2-3'], 'nextPageToken': '3'} page_2 = {'items': ['2-1', '2-2', '2-3'], 'nextPageToken': '3'}
page_3 = {'items': ['3-1', '3-2', '3-3']} page_3 = {'items': ['3-1', '3-2', '3-3']}
self.mock_method.return_value.execute.side_effect = [page_1, page_2, page_3] self.mock_method.return_value.execute.side_effect = [
page_1, page_2, page_3
]
response_items = gapi.get_all_pages(self.mock_service, response_items = gapi.get_all_pages(self.mock_service,
self.mock_method_name) self.mock_method_name)
self.assertListEqual(response_items, self.assertListEqual(
page_1['items'] + page_2['items'] + page_3['items']) response_items, page_1['items'] + page_2['items'] + page_3['items'])
def test_get_all_pages_includes_next_pagetoken_in_request(self): def test_get_all_pages_includes_next_pagetoken_in_request(self):
page_1 = {'items': ['1-1', '1-2', '1-3'], 'nextPageToken': 'someToken'} page_1 = {'items': ['1-1', '1-2', '1-3'], 'nextPageToken': 'someToken'}
page_2 = {'items': ['2-1', '2-2', '2-3']} page_2 = {'items': ['2-1', '2-2', '2-3']}
self.mock_method.return_value.execute.side_effect = [page_1, page_2] self.mock_method.return_value.execute.side_effect = [page_1, page_2]
gapi.get_all_pages(self.mock_service, self.mock_method_name, pageSize=100) gapi.get_all_pages(self.mock_service,
self.mock_method_name,
pageSize=100)
self.assertEqual(self.mock_method.call_count, 2) self.assertEqual(self.mock_method.call_count, 2)
call_2_kwargs = self.mock_method.call_args_list[1][1] call_2_kwargs = self.mock_method.call_args_list[1][1]
self.assertIn('pageToken', call_2_kwargs) self.assertIn('pageToken', call_2_kwargs)
@@ -321,8 +333,9 @@ class GapiTest(unittest.TestCase):
def test_get_all_pages_max_page_size_overrided(self): def test_get_all_pages_max_page_size_overrided(self):
self.mock_method.return_value.execute.return_value = self.empty_items_response self.mock_method.return_value.execute.return_value = self.empty_items_response
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, pageSize=123456) self.mock_method_name,
pageSize=123456)
request_method_kwargs = self.mock_method.call_args[1] request_method_kwargs = self.mock_method.call_args[1]
self.assertIn('pageSize', request_method_kwargs) self.assertIn('pageSize', request_method_kwargs)
self.assertEqual(123456, request_method_kwargs['pageSize']) self.assertEqual(123456, request_method_kwargs['pageSize'])
@@ -332,8 +345,9 @@ class GapiTest(unittest.TestCase):
paging_message = 'A simple string displayed during paging' paging_message = 'A simple string displayed during paging'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, page_message=paging_message) self.mock_method_name,
page_message=paging_message)
messages_written = [ messages_written = [
call_args[0][0] for call_args in mock_write.call_args_list call_args[0][0] for call_args in mock_write.call_args_list
] ]
@@ -344,8 +358,9 @@ class GapiTest(unittest.TestCase):
paging_message = 'A simple string displayed during paging' paging_message = 'A simple string displayed during paging'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, page_message=paging_message) self.mock_method_name,
page_message=paging_message)
messages_written = [ messages_written = [
call_args[0][0] for call_args in mock_write.call_args_list call_args[0][0] for call_args in mock_write.call_args_list
] ]
@@ -365,8 +380,9 @@ class GapiTest(unittest.TestCase):
paging_message = 'A simple string displayed during paging' paging_message = 'A simple string displayed during paging'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, page_message=paging_message) self.mock_method_name,
page_message=paging_message)
messages_written = [ messages_written = [
call_args[0][0] for call_args in mock_write.call_args_list call_args[0][0] for call_args in mock_write.call_args_list
] ]
@@ -381,8 +397,9 @@ class GapiTest(unittest.TestCase):
paging_message = 'Total number of items discovered: %%total_items%%' paging_message = 'Total number of items discovered: %%total_items%%'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, page_message=paging_message) self.mock_method_name,
page_message=paging_message)
messages_written = [ messages_written = [
call_args[0][0] for call_args in mock_write.call_args_list call_args[0][0] for call_args in mock_write.call_args_list
@@ -413,8 +430,7 @@ class GapiTest(unittest.TestCase):
paging_message = 'First item in page: %%first_item%%' paging_message = 'First item in page: %%first_item%%'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service,
self.mock_method_name, self.mock_method_name,
page_message=paging_message, page_message=paging_message,
message_attribute='position') message_attribute='position')
@@ -441,8 +457,7 @@ class GapiTest(unittest.TestCase):
paging_message = 'Last item in page: %%last_item%%' paging_message = 'Last item in page: %%last_item%%'
with patch.object(gapi.sys.stderr, 'write') as mock_write: with patch.object(gapi.sys.stderr, 'write') as mock_write:
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service,
self.mock_method_name, self.mock_method_name,
page_message=paging_message, page_message=paging_message,
message_attribute='position') message_attribute='position')
@@ -469,8 +484,10 @@ class GapiTest(unittest.TestCase):
def test_get_all_pages_passes_additional_kwargs_to_service_method(self): def test_get_all_pages_passes_additional_kwargs_to_service_method(self):
self.mock_method.return_value.execute.return_value = self.empty_items_response self.mock_method.return_value.execute.return_value = self.empty_items_response
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, my_param_1=1, my_param_2=2) self.mock_method_name,
my_param_1=1,
my_param_2=2)
method_kwargs = self.mock_method.call_args[1] method_kwargs = self.mock_method.call_args[1]
self.assertEqual(method_kwargs.get('my_param_1'), 1) self.assertEqual(method_kwargs.get('my_param_1'), 1)
self.assertEqual(method_kwargs.get('my_param_2'), 2) self.assertEqual(method_kwargs.get('my_param_2'), 2)
@@ -480,8 +497,7 @@ class GapiTest(unittest.TestCase):
throw_for = MagicMock() throw_for = MagicMock()
retry_for = MagicMock() retry_for = MagicMock()
mock_call.return_value = self.empty_items_response mock_call.return_value = self.empty_items_response
gapi.get_all_pages( gapi.get_all_pages(self.mock_service,
self.mock_service,
self.mock_method_name, self.mock_method_name,
throw_reasons=throw_for, throw_reasons=throw_for,
retry_reasons=retry_for) retry_reasons=retry_for)
@@ -493,8 +509,9 @@ class GapiTest(unittest.TestCase):
field_name = 'things' field_name = 'things'
fake_response = {field_name: [{}, {}, {}]} fake_response = {field_name: [{}, {}, {}]}
self.mock_method.return_value.execute.return_value = fake_response self.mock_method.return_value.execute.return_value = fake_response
page = gapi.get_all_pages( page = gapi.get_all_pages(self.mock_service,
self.mock_service, self.mock_method_name, items=field_name) self.mock_method_name,
items=field_name)
self.assertEqual(page, fake_response[field_name]) self.assertEqual(page, fake_response[field_name])

View File

@@ -24,8 +24,7 @@ def normalizeCalendarId(calname, checkPrimary=False):
def buildCalendarGAPIObject(calname): def buildCalendarGAPIObject(calname):
calendarId = normalizeCalendarId(calname) calendarId = normalizeCalendarId(calname)
return (calendarId, gam.buildGAPIServiceObject('calendar', return (calendarId, gam.buildGAPIServiceObject('calendar', calendarId))
calendarId))
def buildCalendarDataGAPIObject(calname): def buildCalendarDataGAPIObject(calname):
@@ -41,6 +40,7 @@ def buildCalendarDataGAPIObject(calname):
_, cal = buildCalendarGAPIObject(gam._getValueFromOAuth('email')) _, cal = buildCalendarGAPIObject(gam._getValueFromOAuth('email'))
return (calendarId, cal) return (calendarId, cal)
def printShowACLs(csvFormat): def printShowACLs(csvFormat):
calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2])
if not cal: if not cal:
@@ -54,10 +54,9 @@ def printShowACLs(csvFormat):
i += 1 i += 1
else: else:
action = ['showacl', 'printacl'][csvFormat] action = ['showacl', 'printacl'][csvFormat]
message = f"gam calendar <email> {action}" message = f'gam calendar <email> {action}'
controlflow.invalid_argument_exit(sys.argv[i], message) controlflow.invalid_argument_exit(sys.argv[i], message)
acls = gapi.get_all_pages( acls = gapi.get_all_pages(cal.acl(), 'list', 'items', calendarId=calendarId)
cal.acl(), 'list', 'items', calendarId=calendarId)
i = 0 i = 0
if csvFormat: if csvFormat:
titles = [] titles = []
@@ -75,10 +74,11 @@ def printShowACLs(csvFormat):
else: else:
formatted_acl = formatACLRule(rule) formatted_acl = formatACLRule(rule)
current_count = display.current_count(i, count) current_count = display.current_count(i, count)
print(f'Calendar: {calendarId}, ACL: {formatted_acl}{current_count}') print(
f'Calendar: {calendarId}, ACL: {formatted_acl}{current_count}')
if csvFormat: if csvFormat:
display.write_csv_file( display.write_csv_file(rows, titles, f'{calendarId} Calendar ACLs',
rows, titles, f'{calendarId} Calendar ACLs', toDrive) toDrive)
def _getCalendarACLScope(i, body): def _getCalendarACLScope(i, body):
@@ -87,8 +87,8 @@ def _getCalendarACLScope(i, body):
body['scope']['type'] = myarg body['scope']['type'] = myarg
i += 1 i += 1
if myarg in ['user', 'group']: if myarg in ['user', 'group']:
body['scope']['value'] = gam.normalizeEmailAddressOrUID( body['scope']['value'] = gam.normalizeEmailAddressOrUID(sys.argv[i],
sys.argv[i], noUid=True) noUid=True)
i += 1 i += 1
elif myarg == 'domain': elif myarg == 'domain':
if i < len(sys.argv) and \ if i < len(sys.argv) and \
@@ -99,8 +99,8 @@ def _getCalendarACLScope(i, body):
body['scope']['value'] = GC_Values[GC_DOMAIN] body['scope']['value'] = GC_Values[GC_DOMAIN]
elif myarg != 'default': elif myarg != 'default':
body['scope']['type'] = 'user' body['scope']['type'] = 'user'
body['scope']['value'] = gam.normalizeEmailAddressOrUID( body['scope']['value'] = gam.normalizeEmailAddressOrUID(myarg,
myarg, noUid=True) noUid=True)
return i return i
@@ -122,22 +122,26 @@ def addACL(function):
return return
myarg = sys.argv[4].lower().replace('_', '') myarg = sys.argv[4].lower().replace('_', '')
if myarg not in CALENDAR_ACL_ROLES_MAP: if myarg not in CALENDAR_ACL_ROLES_MAP:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('Role',
"Role", ", ".join(CALENDAR_ACL_ROLES_MAP), myarg) ', '.join(CALENDAR_ACL_ROLES_MAP),
myarg)
body = {'role': CALENDAR_ACL_ROLES_MAP[myarg]} body = {'role': CALENDAR_ACL_ROLES_MAP[myarg]}
i = _getCalendarACLScope(5, body) i = _getCalendarACLScope(5, body)
sendNotifications = True sendNotifications = True
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'sendnotifications': if myarg == 'sendnotifications':
sendNotifications = gam.getBoolean(sys.argv[i+1], myarg) sendNotifications = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
sys.argv[i], f"gam calendar <email> {function.lower()}") sys.argv[i], f'gam calendar <email> {function.lower()}')
print(f'Calendar: {calendarId}, {function} ACL: {formatACLRule(body)}') print(f'Calendar: {calendarId}, {function} ACL: {formatACLRule(body)}')
gapi.call(cal.acl(), 'insert', calendarId=calendarId, gapi.call(cal.acl(),
body=body, sendNotifications=sendNotifications) 'insert',
calendarId=calendarId,
body=body,
sendNotifications=sendNotifications)
def delACL(): def delACL():
@@ -152,8 +156,11 @@ def delACL():
body = {'role': 'none'} body = {'role': 'none'}
_getCalendarACLScope(5, body) _getCalendarACLScope(5, body)
print(f'Calendar: {calendarId}, Delete ACL: {formatACLScope(body)}') print(f'Calendar: {calendarId}, Delete ACL: {formatACLScope(body)}')
gapi.call(cal.acl(), 'insert', calendarId=calendarId, gapi.call(cal.acl(),
body=body, sendNotifications=False) 'insert',
calendarId=calendarId,
body=body,
sendNotifications=False)
def wipeData(): def wipeData():
@@ -176,7 +183,7 @@ def printEvents():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'query': if myarg == 'query':
q = sys.argv[i+1] q = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'includedeleted': elif myarg == 'includedeleted':
showDeleted = True showDeleted = True
@@ -185,30 +192,34 @@ def printEvents():
showHiddenInvitations = True showHiddenInvitations = True
i += 1 i += 1
elif myarg == 'after': elif myarg == 'after':
timeMin = utils.get_time_or_delta_from_now(sys.argv[i+1]) timeMin = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'before': elif myarg == 'before':
timeMax = utils.get_time_or_delta_from_now(sys.argv[i+1]) timeMax = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'timezone': elif myarg == 'timezone':
timeZone = sys.argv[i+1] timeZone = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'updated': elif myarg == 'updated':
updatedMin = utils.get_time_or_delta_from_now(sys.argv[i+1]) updatedMin = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'todrive': elif myarg == 'todrive':
toDrive = True toDrive = True
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
sys.argv[i], "gam calendar <email> printevents") sys.argv[i], 'gam calendar <email> printevents')
page_message = gapi.got_total_items_msg(f'Events for {calendarId}', '') page_message = gapi.got_total_items_msg(f'Events for {calendarId}', '')
results = gapi.get_all_pages(cal.events(), 'list', 'items', results = gapi.get_all_pages(cal.events(),
'list',
'items',
page_message=page_message, page_message=page_message,
calendarId=calendarId, q=q, calendarId=calendarId,
q=q,
showDeleted=showDeleted, showDeleted=showDeleted,
showHiddenInvitations=showHiddenInvitations, showHiddenInvitations=showHiddenInvitations,
timeMin=timeMin, timeMax=timeMax, timeMin=timeMin,
timeMax=timeMax,
timeZone=timeZone, timeZone=timeZone,
updatedMin=updatedMin) updatedMin=updatedMin)
for result in results: for result in results:
@@ -237,17 +248,19 @@ def getSendUpdates(myarg, i, cal):
sendUpdates = 'all' sendUpdates = 'all'
i += 1 i += 1
elif myarg == 'sendnotifications': elif myarg == 'sendnotifications':
sendUpdates = 'all' if gam.getBoolean(sys.argv[i+1], myarg) else 'none' sendUpdates = 'all' if gam.getBoolean(sys.argv[i +
1], myarg) else 'none'
i += 2 i += 2
else: # 'sendupdates': else: # 'sendupdates':
sendUpdatesMap = {} sendUpdatesMap = {}
for val in cal._rootDesc['resources']['events']['methods']['delete'][ for val in cal._rootDesc['resources']['events']['methods']['delete'][
'parameters']['sendUpdates']['enum']: 'parameters']['sendUpdates']['enum']:
sendUpdatesMap[val.lower()] = val sendUpdatesMap[val.lower()] = val
sendUpdates = sendUpdatesMap.get(sys.argv[i+1].lower(), False) sendUpdates = sendUpdatesMap.get(sys.argv[i + 1].lower(), False)
if not sendUpdates: if not sendUpdates:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('sendupdates',
"sendupdates", ", ".join(sendUpdatesMap), sys.argv[i+1]) ', '.join(sendUpdatesMap),
sys.argv[i + 1])
i += 2 i += 2
return (sendUpdates, i) return (sendUpdates, i)
@@ -265,7 +278,7 @@ def moveOrDeleteEvent(moveOrDelete):
if myarg in ['notifyattendees', 'sendnotifications', 'sendupdates']: if myarg in ['notifyattendees', 'sendnotifications', 'sendupdates']:
sendUpdates, i = getSendUpdates(myarg, i, cal) sendUpdates, i = getSendUpdates(myarg, i, cal)
elif myarg in ['id', 'eventid']: elif myarg in ['id', 'eventid']:
eventId = sys.argv[i+1] eventId = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['query', 'eventquery']: elif myarg in ['query', 'eventquery']:
controlflow.system_error_exit( controlflow.system_error_exit(
@@ -276,15 +289,19 @@ def moveOrDeleteEvent(moveOrDelete):
doit = True doit = True
i += 1 i += 1
elif moveOrDelete == 'move' and myarg == 'destination': elif moveOrDelete == 'move' and myarg == 'destination':
kwargs['destination'] = sys.argv[i+1] kwargs['destination'] = sys.argv[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
sys.argv[i], f"gam calendar <email> {moveOrDelete}event") sys.argv[i], f'gam calendar <email> {moveOrDelete}event')
if doit: if doit:
print(f' going to {moveOrDelete} eventId {eventId}') print(f' going to {moveOrDelete} eventId {eventId}')
gapi.call(cal.events(), moveOrDelete, calendarId=calendarId, gapi.call(cal.events(),
eventId=eventId, sendUpdates=sendUpdates, **kwargs) moveOrDelete,
calendarId=calendarId,
eventId=eventId,
sendUpdates=sendUpdates,
**kwargs)
else: else:
print( print(
f' would {moveOrDelete} eventId {eventId}. Add doit to command ' \ f' would {moveOrDelete} eventId {eventId}. Add doit to command ' \
@@ -296,8 +313,10 @@ def infoEvent():
if not cal: if not cal:
return return
eventId = sys.argv[4] eventId = sys.argv[4]
result = gapi.call(cal.events(), 'get', result = gapi.call(cal.events(),
calendarId=calendarId, eventId=eventId) 'get',
calendarId=calendarId,
eventId=eventId)
display.print_json(result) display.print_json(result)
@@ -316,25 +335,36 @@ def addOrUpdateEvent(action):
kwargs = {'eventId': eventId} kwargs = {'eventId': eventId}
i = 5 i = 5
func = 'patch' func = 'patch'
requires_full_update = ['attendee', 'optionalattendee', requires_full_update = [
'removeattendee', 'replacedescription'] 'attendee', 'optionalattendee', 'removeattendee',
'replacedescription'
]
for arg in sys.argv[i:]: for arg in sys.argv[i:]:
if arg.replace('_', '').lower() in requires_full_update: if arg.replace('_', '').lower() in requires_full_update:
func = 'update' func = 'update'
body = gapi.call(cal.events(), 'get', body = gapi.call(cal.events(),
calendarId=calendarId, eventId=eventId) 'get',
calendarId=calendarId,
eventId=eventId)
break break
sendUpdates, body = getEventAttributes(i, calendarId, cal, body, action) sendUpdates, body = getEventAttributes(i, calendarId, cal, body, action)
result = gapi.call(cal.events(), func, conferenceDataVersion=1, result = gapi.call(cal.events(),
supportsAttachments=True, calendarId=calendarId, func,
sendUpdates=sendUpdates, body=body, fields='id', conferenceDataVersion=1,
supportsAttachments=True,
calendarId=calendarId,
sendUpdates=sendUpdates,
body=body,
fields='id',
**kwargs) **kwargs)
print(f'Event {result["id"]} {action} finished') print(f'Event {result["id"]} {action} finished')
def _remove_attendee(attendees, remove_email): def _remove_attendee(attendees, remove_email):
return [attendee for attendee in attendees return [
if not attendee['email'].lower() == remove_email] attendee for attendee in attendees
if not attendee['email'].lower() == remove_email
]
def getEventAttributes(i, calendarId, cal, body, action): def getEventAttributes(i, calendarId, cal, body, action):
@@ -348,45 +378,48 @@ def getEventAttributes(i, calendarId, cal, body, action):
sendUpdates, i = getSendUpdates(myarg, i, cal) sendUpdates, i = getSendUpdates(myarg, i, cal)
elif myarg == 'attendee': elif myarg == 'attendee':
body.setdefault('attendees', []) body.setdefault('attendees', [])
body['attendees'].append({'email': sys.argv[i+1]}) body['attendees'].append({'email': sys.argv[i + 1]})
i += 2 i += 2
elif myarg == 'removeattendee' and action == 'update': elif myarg == 'removeattendee' and action == 'update':
remove_email = sys.argv[i+1].lower() remove_email = sys.argv[i + 1].lower()
if 'attendees' in body: if 'attendees' in body:
body['attendees'] = _remove_attendee(body['attendees'], body['attendees'] = _remove_attendee(body['attendees'],
remove_email) remove_email)
i += 2 i += 2
elif myarg == 'optionalattendee': elif myarg == 'optionalattendee':
body.setdefault('attendees', []) body.setdefault('attendees', [])
body['attendees'].append( body['attendees'].append({
{'email': sys.argv[i+1], 'optional': True}) 'email': sys.argv[i + 1],
'optional': True
})
i += 2 i += 2
elif myarg == 'anyonecanaddself': elif myarg == 'anyonecanaddself':
body['anyoneCanAddSelf'] = True body['anyoneCanAddSelf'] = True
i += 1 i += 1
elif myarg == 'description': elif myarg == 'description':
body['description'] = sys.argv[i+1].replace('\\n', '\n') body['description'] = sys.argv[i + 1].replace('\\n', '\n')
i += 2 i += 2
elif myarg == 'replacedescription' and action == 'update': elif myarg == 'replacedescription' and action == 'update':
search = sys.argv[i+1] search = sys.argv[i + 1]
replace = sys.argv[i+2] replace = sys.argv[i + 2]
if 'description' in body: if 'description' in body:
body['description'] = re.sub(search, replace, body['description']) body['description'] = re.sub(search, replace,
body['description'])
i += 3 i += 3
elif myarg == 'start': elif myarg == 'start':
if sys.argv[i+1].lower() == 'allday': if sys.argv[i + 1].lower() == 'allday':
body['start'] = {'date': utils.get_yyyymmdd(sys.argv[i+2])} body['start'] = {'date': utils.get_yyyymmdd(sys.argv[i + 2])}
i += 3 i += 3
else: else:
start_time = utils.get_time_or_delta_from_now(sys.argv[i+1]) start_time = utils.get_time_or_delta_from_now(sys.argv[i + 1])
body['start'] = {'dateTime': start_time} body['start'] = {'dateTime': start_time}
i += 2 i += 2
elif myarg == 'end': elif myarg == 'end':
if sys.argv[i+1].lower() == 'allday': if sys.argv[i + 1].lower() == 'allday':
body['end'] = {'date': utils.get_yyyymmdd(sys.argv[i+2])} body['end'] = {'date': utils.get_yyyymmdd(sys.argv[i + 2])}
i += 3 i += 3
else: else:
end_time = utils.get_time_or_delta_from_now(sys.argv[i+1]) end_time = utils.get_time_or_delta_from_now(sys.argv[i + 1])
body['end'] = {'dateTime': end_time} body['end'] = {'dateTime': end_time}
i += 2 i += 2
elif myarg == 'guestscantinviteothers': elif myarg == 'guestscantinviteothers':
@@ -394,64 +427,66 @@ def getEventAttributes(i, calendarId, cal, body, action):
i += 1 i += 1
elif myarg == 'guestscaninviteothers': elif myarg == 'guestscaninviteothers':
body['guestsCanInviteTohters'] = gam.getBoolean( body['guestsCanInviteTohters'] = gam.getBoolean(
sys.argv[i+1], 'guestscaninviteothers') sys.argv[i + 1], 'guestscaninviteothers')
i += 2 i += 2
elif myarg == 'guestscantseeothers': elif myarg == 'guestscantseeothers':
body['guestsCanSeeOtherGuests'] = False body['guestsCanSeeOtherGuests'] = False
i += 1 i += 1
elif myarg == 'guestscanseeothers': elif myarg == 'guestscanseeothers':
body['guestsCanSeeOtherGuests'] = gam.getBoolean( body['guestsCanSeeOtherGuests'] = gam.getBoolean(
sys.argv[i+1], 'guestscanseeothers') sys.argv[i + 1], 'guestscanseeothers')
i += 2 i += 2
elif myarg == 'guestscanmodify': elif myarg == 'guestscanmodify':
body['guestsCanModify'] = gam.getBoolean( body['guestsCanModify'] = gam.getBoolean(sys.argv[i + 1],
sys.argv[i+1], 'guestscanmodify') 'guestscanmodify')
i += 2 i += 2
elif myarg == 'id': elif myarg == 'id':
if action == 'update': if action == 'update':
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
'id', 'gam calendar <calendar> updateevent') 'id', 'gam calendar <calendar> updateevent')
body['id'] = sys.argv[i+1] body['id'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'summary': elif myarg == 'summary':
body['summary'] = sys.argv[i+1] body['summary'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'location': elif myarg == 'location':
body['location'] = sys.argv[i+1] body['location'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'available': elif myarg == 'available':
body['transparency'] = 'transparent' body['transparency'] = 'transparent'
i += 1 i += 1
elif myarg == 'transparency': elif myarg == 'transparency':
validTransparency = ['opaque', 'transparent'] validTransparency = ['opaque', 'transparent']
if sys.argv[i+1].lower() in validTransparency: if sys.argv[i + 1].lower() in validTransparency:
body['transparency'] = sys.argv[i+1].lower() body['transparency'] = sys.argv[i + 1].lower()
else: else:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('transparency',
'transparency', ', '.join(validTransparency),
", ".join(validTransparency), sys.argv[i+1]) sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'visibility': elif myarg == 'visibility':
validVisibility = ['default', 'public', 'private'] validVisibility = ['default', 'public', 'private']
if sys.argv[i+1].lower() in validVisibility: if sys.argv[i + 1].lower() in validVisibility:
body['visibility'] = sys.argv[i+1].lower() body['visibility'] = sys.argv[i + 1].lower()
else: else:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('visibility',
"visibility", ", ".join(validVisibility), sys.argv[i+1]) ', '.join(validVisibility),
sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'tentative': elif myarg == 'tentative':
body['status'] = 'tentative' body['status'] = 'tentative'
i += 1 i += 1
elif myarg == 'status': elif myarg == 'status':
validStatus = ['confirmed', 'tentative', 'cancelled'] validStatus = ['confirmed', 'tentative', 'cancelled']
if sys.argv[i+1].lower() in validStatus: if sys.argv[i + 1].lower() in validStatus:
body['status'] = sys.argv[i+1].lower() body['status'] = sys.argv[i + 1].lower()
else: else:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('visibility',
'visibility', ', '.join(validStatus), sys.argv[i+1]) ', '.join(validStatus),
sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'source': elif myarg == 'source':
body['source'] = {'title': sys.argv[i+1], 'url': sys.argv[i+2]} body['source'] = {'title': sys.argv[i + 1], 'url': sys.argv[i + 2]}
i += 3 i += 3
elif myarg == 'noreminders': elif myarg == 'noreminders':
body['reminders'] = {'useDefault': False} body['reminders'] = {'useDefault': False}
@@ -460,43 +495,48 @@ def getEventAttributes(i, calendarId, cal, body, action):
minutes = \ minutes = \
gam.getInteger(sys.argv[i+1], myarg, minVal=0, gam.getInteger(sys.argv[i+1], myarg, minVal=0,
maxVal=CALENDAR_REMINDER_MAX_MINUTES) maxVal=CALENDAR_REMINDER_MAX_MINUTES)
reminder = {'minutes': minutes, 'method': sys.argv[i+2]} reminder = {'minutes': minutes, 'method': sys.argv[i + 2]}
body.setdefault( body.setdefault('reminders', {'overrides': [], 'useDefault': False})
'reminders', {'overrides': [], 'useDefault': False})
body['reminders']['overrides'].append(reminder) body['reminders']['overrides'].append(reminder)
i += 3 i += 3
elif myarg == 'recurrence': elif myarg == 'recurrence':
body.setdefault('recurrence', []) body.setdefault('recurrence', [])
body['recurrence'].append(sys.argv[i+1]) body['recurrence'].append(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'timezone': elif myarg == 'timezone':
timeZone = sys.argv[i+1] timeZone = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'privateproperty': elif myarg == 'privateproperty':
if 'extendedProperties' not in body: if 'extendedProperties' not in body:
body['extendedProperties'] = {'private': {}, 'shared': {}} body['extendedProperties'] = {'private': {}, 'shared': {}}
body['extendedProperties']['private'][sys.argv[i+1]] = sys.argv[i+2] body['extendedProperties']['private'][sys.argv[i +
1]] = sys.argv[i + 2]
i += 3 i += 3
elif myarg == 'sharedproperty': elif myarg == 'sharedproperty':
if 'extendedProperties' not in body: if 'extendedProperties' not in body:
body['extendedProperties'] = {'private': {}, 'shared': {}} body['extendedProperties'] = {'private': {}, 'shared': {}}
body['extendedProperties']['shared'][sys.argv[i+1]] = sys.argv[i+2] body['extendedProperties']['shared'][sys.argv[i + 1]] = sys.argv[i +
2]
i += 3 i += 3
elif myarg == 'colorindex': elif myarg == 'colorindex':
body['colorId'] = gam.getInteger( body['colorId'] = gam.getInteger(sys.argv[i + 1], myarg,
sys.argv[i+1], myarg, CALENDAR_EVENT_MIN_COLOR_INDEX, CALENDAR_EVENT_MIN_COLOR_INDEX,
CALENDAR_EVENT_MAX_COLOR_INDEX) CALENDAR_EVENT_MAX_COLOR_INDEX)
i += 2 i += 2
elif myarg == 'hangoutsmeet': elif myarg == 'hangoutsmeet':
body['conferenceData'] = {'createRequest': { body['conferenceData'] = {
'requestId': f'{str(uuid.uuid4())}'}} 'createRequest': {
'requestId': f'{str(uuid.uuid4())}'
}
}
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
sys.argv[i], f'gam calendar <email> {action}event') sys.argv[i], f'gam calendar <email> {action}event')
if ('recurrence' in body) and (('start' in body) or ('end' in body)): if ('recurrence' in body) and (('start' in body) or ('end' in body)):
if not timeZone: if not timeZone:
timeZone = gapi.call(cal.calendars(), 'get', timeZone = gapi.call(cal.calendars(),
'get',
calendarId=calendarId, calendarId=calendarId,
fields='timeZone')['timeZone'] fields='timeZone')['timeZone']
if 'start' in body: if 'start' in body:
@@ -515,20 +555,20 @@ def modifySettings():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'description': if myarg == 'description':
body['description'] = sys.argv[i+1] body['description'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'location': elif myarg == 'location':
body['location'] = sys.argv[i+1] body['location'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'summary': elif myarg == 'summary':
body['summary'] = sys.argv[i+1] body['summary'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'timezone': elif myarg == 'timezone':
body['timeZone'] = sys.argv[i+1] body['timeZone'] = sys.argv[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam calendar <email> modify") 'gam calendar <email> modify')
gapi.call(cal.calendars(), 'patch', calendarId=calendarId, body=body) gapi.call(cal.calendars(), 'patch', calendarId=calendarId, body=body)
@@ -540,23 +580,23 @@ def changeAttendees(users):
while len(sys.argv) > i: while len(sys.argv) > i:
myarg = sys.argv[i].lower() myarg = sys.argv[i].lower()
if myarg == 'csv': if myarg == 'csv':
csv_file = sys.argv[i+1] csv_file = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'dryrun': elif myarg == 'dryrun':
do_it = False do_it = False
i += 1 i += 1
elif myarg == 'start': elif myarg == 'start':
start_date = utils.get_time_or_delta_from_now(sys.argv[i+1]) start_date = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'end': elif myarg == 'end':
end_date = utils.get_time_or_delta_from_now(sys.argv[i+1]) end_date = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'allevents': elif myarg == 'allevents':
allevents = True allevents = True
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(
sys.argv[i], "gam <users> update calattendees") sys.argv[i], 'gam <users> update calattendees')
attendee_map = {} attendee_map = {}
f = fileutils.open_file(csv_file) f = fileutils.open_file(csv_file)
csvFile = csv.reader(f) csvFile = csv.reader(f)
@@ -570,9 +610,13 @@ def changeAttendees(users):
continue continue
page_token = None page_token = None
while True: while True:
events_page = gapi.call(cal.events(), 'list', calendarId=user, events_page = gapi.call(cal.events(),
pageToken=page_token, timeMin=start_date, 'list',
timeMax=end_date, showDeleted=False, calendarId=user,
pageToken=page_token,
timeMin=start_date,
timeMax=end_date,
showDeleted=False,
showHiddenInvitations=False) showHiddenInvitations=False)
print(f'Got {len(events_page.get("items", []))}') print(f'Got {len(events_page.get("items", []))}')
for event in events_page.get('items', []): for event in events_page.get('items', []):
@@ -596,8 +640,8 @@ def changeAttendees(users):
try: try:
if attendee['email'].lower() in attendee_map: if attendee['email'].lower() in attendee_map:
old_email = attendee['email'].lower() old_email = attendee['email'].lower()
new_email = attendee_map[attendee['email'].lower( new_email = attendee_map[
)] attendee['email'].lower()]
print(f' SWITCHING attendee {old_email} to ' \ print(f' SWITCHING attendee {old_email} to ' \
f'{new_email} for {event_summary}') f'{new_email} for {event_summary}')
event['attendees'].remove(attendee) event['attendees'].remove(attendee)
@@ -612,9 +656,12 @@ def changeAttendees(users):
body['attendees'] = event['attendees'] body['attendees'] = event['attendees']
print(f'UPDATING {event_summary}') print(f'UPDATING {event_summary}')
if do_it: if do_it:
gapi.call(cal.events(), 'patch', calendarId=user, gapi.call(cal.events(),
'patch',
calendarId=user,
eventId=event['id'], eventId=event['id'],
sendNotifications=False, body=body) sendNotifications=False,
body=body)
else: else:
print(' not pulling the trigger.') print(' not pulling the trigger.')
# else: # else:
@@ -631,8 +678,10 @@ def deleteCalendar(users):
user, cal = buildCalendarGAPIObject(user) user, cal = buildCalendarGAPIObject(user)
if not cal: if not cal:
continue continue
gapi.call(cal.calendarList(), 'delete', gapi.call(cal.calendarList(),
soft_errors=True, calendarId=calendarId) 'delete',
soft_errors=True,
calendarId=calendarId)
CALENDAR_REMINDER_MAX_MINUTES = 40320 CALENDAR_REMINDER_MAX_MINUTES = 40320
@@ -649,62 +698,71 @@ def getCalendarAttributes(i, body, function):
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'selected': if myarg == 'selected':
body['selected'] = gam.getBoolean(sys.argv[i+1], myarg) body['selected'] = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2 i += 2
elif myarg == 'hidden': elif myarg == 'hidden':
body['hidden'] = gam.getBoolean(sys.argv[i+1], myarg) body['hidden'] = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2 i += 2
elif myarg == 'summary': elif myarg == 'summary':
body['summaryOverride'] = sys.argv[i+1] body['summaryOverride'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'colorindex': elif myarg == 'colorindex':
body['colorId'] = gam.getInteger( body['colorId'] = gam.getInteger(sys.argv[i + 1],
sys.argv[i+1], myarg, minVal=CALENDAR_MIN_COLOR_INDEX, myarg,
minVal=CALENDAR_MIN_COLOR_INDEX,
maxVal=CALENDAR_MAX_COLOR_INDEX) maxVal=CALENDAR_MAX_COLOR_INDEX)
i += 2 i += 2
elif myarg == 'backgroundcolor': elif myarg == 'backgroundcolor':
body['backgroundColor'] = gam.getColor(sys.argv[i+1]) body['backgroundColor'] = gam.getColor(sys.argv[i + 1])
colorRgbFormat = True colorRgbFormat = True
i += 2 i += 2
elif myarg == 'foregroundcolor': elif myarg == 'foregroundcolor':
body['foregroundColor'] = gam.getColor(sys.argv[i+1]) body['foregroundColor'] = gam.getColor(sys.argv[i + 1])
colorRgbFormat = True colorRgbFormat = True
i += 2 i += 2
elif myarg == 'reminder': elif myarg == 'reminder':
body.setdefault('defaultReminders', []) body.setdefault('defaultReminders', [])
method = sys.argv[i+1].lower() method = sys.argv[i + 1].lower()
if method not in CLEAR_NONE_ARGUMENT: if method not in CLEAR_NONE_ARGUMENT:
if method not in CALENDAR_REMINDER_METHODS: if method not in CALENDAR_REMINDER_METHODS:
controlflow.expected_argument_exit("Method", ", ".join( controlflow.expected_argument_exit(
CALENDAR_REMINDER_METHODS+CLEAR_NONE_ARGUMENT), method) 'Method', ', '.join(CALENDAR_REMINDER_METHODS +
minutes = gam.getInteger( CLEAR_NONE_ARGUMENT), method)
sys.argv[i+2], myarg, minVal=0, minutes = gam.getInteger(sys.argv[i + 2],
myarg,
minVal=0,
maxVal=CALENDAR_REMINDER_MAX_MINUTES) maxVal=CALENDAR_REMINDER_MAX_MINUTES)
body['defaultReminders'].append( body['defaultReminders'].append({
{'method': method, 'minutes': minutes}) 'method': method,
'minutes': minutes
})
i += 3 i += 3
else: else:
i += 2 i += 2
elif myarg == 'notification': elif myarg == 'notification':
body.setdefault('notificationSettings', {'notifications': []}) body.setdefault('notificationSettings', {'notifications': []})
method = sys.argv[i+1].lower() method = sys.argv[i + 1].lower()
if method not in CLEAR_NONE_ARGUMENT: if method not in CLEAR_NONE_ARGUMENT:
if method not in CALENDAR_NOTIFICATION_METHODS: if method not in CALENDAR_NOTIFICATION_METHODS:
controlflow.expected_argument_exit("Method", ", ".join( controlflow.expected_argument_exit(
CALENDAR_NOTIFICATION_METHODS+CLEAR_NONE_ARGUMENT), method) 'Method', ', '.join(CALENDAR_NOTIFICATION_METHODS +
eventType = sys.argv[i+2].lower() CLEAR_NONE_ARGUMENT), method)
eventType = sys.argv[i + 2].lower()
if eventType not in CALENDAR_NOTIFICATION_TYPES_MAP: if eventType not in CALENDAR_NOTIFICATION_TYPES_MAP:
controlflow.expected_argument_exit("Event", ", ".join( controlflow.expected_argument_exit(
CALENDAR_NOTIFICATION_TYPES_MAP), eventType) 'Event', ', '.join(CALENDAR_NOTIFICATION_TYPES_MAP),
notice = {'method': method, eventType)
'type': CALENDAR_NOTIFICATION_TYPES_MAP[eventType]} notice = {
'method': method,
'type': CALENDAR_NOTIFICATION_TYPES_MAP[eventType]
}
body['notificationSettings']['notifications'].append(notice) body['notificationSettings']['notifications'].append(notice)
i += 3 i += 3
else: else:
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], f"gam {function} calendar") f'gam {function} calendar')
return colorRgbFormat return colorRgbFormat
@@ -721,8 +779,11 @@ def addCalendar(users):
continue continue
current_count = display.current_count(i, count) current_count = display.current_count(i, count)
print(f'Subscribing {user} to calendar {calendarId}{current_count}') print(f'Subscribing {user} to calendar {calendarId}{current_count}')
gapi.call(cal.calendarList(), 'insert', soft_errors=True, gapi.call(cal.calendarList(),
body=body, colorRgbFormat=colorRgbFormat) 'insert',
soft_errors=True,
body=body,
colorRgbFormat=colorRgbFormat)
def updateCalendar(users): def updateCalendar(users):
@@ -740,13 +801,17 @@ def updateCalendar(users):
print(f"Updating {user}'s subscription to calendar ' \ print(f"Updating {user}'s subscription to calendar ' \
f'{calendarId}{current_count}") f'{calendarId}{current_count}")
calId = calendarId if calendarId != 'primary' else user calId = calendarId if calendarId != 'primary' else user
gapi.call(cal.calendarList(), 'patch', soft_errors=True, gapi.call(cal.calendarList(),
calendarId=calId, body=body, colorRgbFormat=colorRgbFormat) 'patch',
soft_errors=True,
calendarId=calId,
body=body,
colorRgbFormat=colorRgbFormat)
def _showCalendar(userCalendar, j, jcount): def _showCalendar(userCalendar, j, jcount):
current_count = display.current_count(j, jcount) current_count = display.current_count(j, jcount)
summary = userCalendar.get("summaryOverride", userCalendar["summary"]) summary = userCalendar.get('summaryOverride', userCalendar['summary'])
print(f' Calendar: {userCalendar["id"]}{current_count}') print(f' Calendar: {userCalendar["id"]}{current_count}')
print(f' Summary: {summary}') print(f' Summary: {summary}')
print(f' Description: {userCalendar.get("description", "")}') print(f' Description: {userCalendar.get("description", "")}')
@@ -780,7 +845,8 @@ def infoCalendar(users):
user, cal = buildCalendarGAPIObject(user) user, cal = buildCalendarGAPIObject(user)
if not cal: if not cal:
continue continue
result = gapi.call(cal.calendarList(), 'get', result = gapi.call(cal.calendarList(),
'get',
soft_errors=True, soft_errors=True,
calendarId=calendarId) calendarId=calendarId)
if result: if result:
@@ -809,8 +875,10 @@ def printShowCalendars(users, csvFormat):
user, cal = buildCalendarGAPIObject(user) user, cal = buildCalendarGAPIObject(user)
if not cal: if not cal:
continue continue
result = gapi.get_all_pages( result = gapi.get_all_pages(cal.calendarList(),
cal.calendarList(), 'list', 'items', soft_errors=True) 'list',
'items',
soft_errors=True)
jcount = len(result) jcount = len(result)
if not csvFormat: if not csvFormat:
print(f'User: {user}, Calendars:{display.current_count(i, count)}') print(f'User: {user}, Calendars:{display.current_count(i, count)}')
@@ -825,8 +893,9 @@ def printShowCalendars(users, csvFormat):
continue continue
for userCalendar in result: for userCalendar in result:
row = {'primaryEmail': user} row = {'primaryEmail': user}
display.add_row_titles_to_csv_file(utils.flatten_json( display.add_row_titles_to_csv_file(
userCalendar, flattened=row), csvRows, titles) utils.flatten_json(userCalendar, flattened=row), csvRows,
titles)
if csvFormat: if csvFormat:
display.sort_csv_titles(['primaryEmail', 'id'], titles) display.sort_csv_titles(['primaryEmail', 'id'], titles)
display.write_csv_file(csvRows, titles, 'Calendars', todrive) display.write_csv_file(csvRows, titles, 'Calendars', todrive)
@@ -840,8 +909,10 @@ def showCalSettings(users):
user, cal = buildCalendarGAPIObject(user) user, cal = buildCalendarGAPIObject(user)
if not cal: if not cal:
continue continue
feed = gapi.get_all_pages( feed = gapi.get_all_pages(cal.settings(),
cal.settings(), 'list', 'items', soft_errors=True) 'list',
'items',
soft_errors=True)
if feed: if feed:
current_count = display.current_count(i, count) current_count = display.current_count(i, count)
print(f'User: {user}, Calendar Settings:{current_count}') print(f'User: {user}, Calendar Settings:{current_count}')
@@ -862,11 +933,11 @@ def transferSecCals(users):
remove_source_user = False remove_source_user = False
i += 1 i += 1
elif myarg == 'sendnotifications': elif myarg == 'sendnotifications':
sendNotifications = gam.getBoolean(sys.argv[i+1], myarg) sendNotifications = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam <users> transfer seccals") 'gam <users> transfer seccals')
if remove_source_user: if remove_source_user:
target_user, target_cal = buildCalendarGAPIObject(target_user) target_user, target_cal = buildCalendarGAPIObject(target_user)
if not target_cal: if not target_cal:
@@ -875,20 +946,38 @@ def transferSecCals(users):
user, source_cal = buildCalendarGAPIObject(user) user, source_cal = buildCalendarGAPIObject(user)
if not source_cal: if not source_cal:
continue continue
calendars = gapi.get_all_pages(source_cal.calendarList(), 'list', calendars = gapi.get_all_pages(source_cal.calendarList(),
'items', soft_errors=True, 'list',
minAccessRole='owner', showHidden=True, 'items',
soft_errors=True,
minAccessRole='owner',
showHidden=True,
fields='items(id),nextPageToken') fields='items(id),nextPageToken')
for calendar in calendars: for calendar in calendars:
calendarId = calendar['id'] calendarId = calendar['id']
if calendarId.find('@group.calendar.google.com') != -1: if calendarId.find('@group.calendar.google.com') != -1:
body = {'role': 'owner', body = {
'scope': {'type': 'user', 'value': target_user}} 'role': 'owner',
gapi.call(source_cal.acl(), 'insert', calendarId=calendarId, 'scope': {
body=body, sendNotifications=sendNotifications) 'type': 'user',
if remove_source_user: 'value': target_user
body = {'role': 'none', }
'scope': {'type': 'user', 'value': user}} }
gapi.call(target_cal.acl(), 'insert', gapi.call(source_cal.acl(),
calendarId=calendarId, body=body, 'insert',
calendarId=calendarId,
body=body,
sendNotifications=sendNotifications)
if remove_source_user:
body = {
'role': 'none',
'scope': {
'type': 'user',
'value': user
}
}
gapi.call(target_cal.acl(),
'insert',
calendarId=calendarId,
body=body,
sendNotifications=sendNotifications) sendNotifications=sendNotifications)

View File

@@ -20,29 +20,33 @@ def doUpdateCros():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'user': if myarg == 'user':
update_body['annotatedUser'] = sys.argv[i+1] update_body['annotatedUser'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'location': elif myarg == 'location':
update_body['annotatedLocation'] = sys.argv[i+1] update_body['annotatedLocation'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'notes': elif myarg == 'notes':
update_body['notes'] = sys.argv[i+1].replace('\\n', '\n') update_body['notes'] = sys.argv[i + 1].replace('\\n', '\n')
i += 2 i += 2
elif myarg in ['tag', 'asset', 'assetid']: elif myarg in ['tag', 'asset', 'assetid']:
update_body['annotatedAssetId'] = sys.argv[i+1] update_body['annotatedAssetId'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['ou', 'org']: elif myarg in ['ou', 'org']:
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1]) orgUnitPath = gam.getOrgUnitItem(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'action': elif myarg == 'action':
action = sys.argv[i+1].lower().replace('_', '').replace('-', '') action = sys.argv[i + 1].lower().replace('_', '').replace('-', '')
deprovisionReason = None deprovisionReason = None
if action in ['deprovisionsamemodelreplace', if action in [
'deprovisionsamemodelreplacement']: 'deprovisionsamemodelreplace',
'deprovisionsamemodelreplacement'
]:
action = 'deprovision' action = 'deprovision'
deprovisionReason = 'same_model_replacement' deprovisionReason = 'same_model_replacement'
elif action in ['deprovisiondifferentmodelreplace', elif action in [
'deprovisiondifferentmodelreplacement']: 'deprovisiondifferentmodelreplace',
'deprovisiondifferentmodelreplacement'
]:
action = 'deprovision' action = 'deprovision'
deprovisionReason = 'different_model_replacement' deprovisionReason = 'different_model_replacement'
elif action in ['deprovisionretiringdevice']: elif action in ['deprovisionretiringdevice']:
@@ -62,7 +66,7 @@ def doUpdateCros():
ack_wipe = True ack_wipe = True
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam update cros") controlflow.invalid_argument_exit(sys.argv[i], 'gam update cros')
i = 0 i = 0
count = len(devices) count = len(devices)
if action_body: if action_body:
@@ -86,27 +90,33 @@ def doUpdateCros():
i += 1 i += 1
cur_count = gam.currentCount(i, count) cur_count = gam.currentCount(i, count)
print(f' performing action {action} for {deviceId}{cur_count}') print(f' performing action {action} for {deviceId}{cur_count}')
gapi.call(cd.chromeosdevices(), function='action', gapi.call(cd.chromeosdevices(),
function='action',
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
resourceId=deviceId, body=action_body) resourceId=deviceId,
body=action_body)
else: else:
if update_body: if update_body:
for deviceId in devices: for deviceId in devices:
i += 1 i += 1
current_count = gam.currentCount(i, count) current_count = gam.currentCount(i, count)
print(f' updating {deviceId}{current_count}') print(f' updating {deviceId}{current_count}')
gapi.call(cd.chromeosdevices(), 'update', gapi.call(cd.chromeosdevices(),
'update',
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
deviceId=deviceId, body=update_body) deviceId=deviceId,
body=update_body)
if orgUnitPath: if orgUnitPath:
# split moves into max 50 devices per batch # split moves into max 50 devices per batch
for l in range(0, len(devices), 50): for l in range(0, len(devices), 50):
move_body = {'deviceIds': devices[l:l+50]} move_body = {'deviceIds': devices[l:l + 50]}
print(f' moving {len(move_body["deviceIds"])} devices to ' \ print(f' moving {len(move_body["deviceIds"])} devices to ' \
f'{orgUnitPath}') f'{orgUnitPath}')
gapi.call(cd.chromeosdevices(), 'moveDevicesToOu', gapi.call(cd.chromeosdevices(),
'moveDevicesToOu',
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
orgUnitPath=orgUnitPath, body=move_body) orgUnitPath=orgUnitPath,
body=move_body)
def doGetCrosInfo(): def doGetCrosInfo():
@@ -125,13 +135,13 @@ def doGetCrosInfo():
noLists = True noLists = True
i += 1 i += 1
elif myarg == 'listlimit': elif myarg == 'listlimit':
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=-1) listLimit = gam.getInteger(sys.argv[i + 1], myarg, minVal=-1)
i += 2 i += 2
elif myarg in CROS_START_ARGUMENTS: elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i+1]) startDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg in CROS_END_ARGUMENTS: elif myarg in CROS_END_ARGUMENTS:
endDate = _getFilterDate(sys.argv[i+1]) endDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'allfields': elif myarg == 'allfields':
projection = 'FULL' projection = 'FULL'
@@ -148,7 +158,7 @@ def doGetCrosInfo():
fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[myarg]) fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[myarg])
i += 1 i += 1
elif myarg == 'fields': elif myarg == 'fields':
fieldNameList = sys.argv[i+1] fieldNameList = sys.argv[i + 1]
for field in fieldNameList.lower().replace(',', ' ').split(): for field in fieldNameList.lower().replace(',', ' ').split():
if field in CROS_ARGUMENT_TO_PROPERTY_MAP: if field in CROS_ARGUMENT_TO_PROPERTY_MAP:
fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[field]) fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[field])
@@ -158,21 +168,21 @@ def doGetCrosInfo():
projection = 'FULL' projection = 'FULL'
noLists = False noLists = False
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(field,
field, "gam info cros fields") 'gam info cros fields')
i += 2 i += 2
elif myarg == 'downloadfile': elif myarg == 'downloadfile':
downloadfile = sys.argv[i+1] downloadfile = sys.argv[i + 1]
if downloadfile.lower() == 'latest': if downloadfile.lower() == 'latest':
downloadfile = downloadfile.lower() downloadfile = downloadfile.lower()
i += 2 i += 2
elif myarg == 'targetfolder': elif myarg == 'targetfolder':
targetFolder = os.path.expanduser(sys.argv[i+1]) targetFolder = os.path.expanduser(sys.argv[i + 1])
if not os.path.isdir(targetFolder): if not os.path.isdir(targetFolder):
os.makedirs(targetFolder) os.makedirs(targetFolder)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam info cros") controlflow.invalid_argument_exit(sys.argv[i], 'gam info cros')
if fieldsList: if fieldsList:
fieldsList.append('deviceId') fieldsList.append('deviceId')
fields = ','.join(set(fieldsList)).replace('.', '/') fields = ','.join(set(fieldsList)).replace('.', '/')
@@ -182,9 +192,11 @@ def doGetCrosInfo():
device_count = len(devices) device_count = len(devices)
for deviceId in devices: for deviceId in devices:
i += 1 i += 1
cros = gapi.call(cd.chromeosdevices(), 'get', cros = gapi.call(cd.chromeosdevices(),
'get',
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
deviceId=deviceId, projection=projection, deviceId=deviceId,
projection=projection,
fields=fields) fields=fields)
print(f'CrOS Device: {deviceId} ({i} of {device_count})') print(f'CrOS Device: {deviceId} ({i} of {device_count})')
if 'notes' in cros: if 'notes' in cros:
@@ -208,8 +220,8 @@ def doGetCrosInfo():
print(' activeTimeRanges') print(' activeTimeRanges')
num_ranges = min(lenATR, listLimit or lenATR) num_ranges = min(lenATR, listLimit or lenATR)
for activeTimeRange in activeTimeRanges[:num_ranges]: for activeTimeRange in activeTimeRanges[:num_ranges]:
active_date = activeTimeRange["date"] active_date = activeTimeRange['date']
active_time = activeTimeRange["activeTime"] active_time = activeTimeRange['activeTime']
duration = utils.formatMilliSeconds(active_time) duration = utils.formatMilliSeconds(active_time)
minutes = active_time // 60000 minutes = active_time // 60000
print(f' date: {active_date}') print(f' date: {active_date}')
@@ -222,16 +234,17 @@ def doGetCrosInfo():
print(' recentUsers') print(' recentUsers')
num_ranges = min(lenRU, listLimit or lenRU) num_ranges = min(lenRU, listLimit or lenRU)
for recentUser in recentUsers[:num_ranges]: for recentUser in recentUsers[:num_ranges]:
useremail = recentUser.get("email") useremail = recentUser.get('email')
if not useremail: if not useremail:
if recentUser["type"] == "USER_TYPE_UNMANAGED": if recentUser['type'] == 'USER_TYPE_UNMANAGED':
useremail = 'UnmanagedUser' useremail = 'UnmanagedUser'
else: else:
useremail = 'Unknown' useremail = 'Unknown'
print(f' type: {recentUser["type"]}') print(f' type: {recentUser["type"]}')
print(f' email: {useremail}') print(f' email: {useremail}')
deviceFiles = _filterCreateReportTime( deviceFiles = _filterCreateReportTime(cros.get('deviceFiles',
cros.get('deviceFiles', []), 'createTime', startDate, endDate) []), 'createTime',
startDate, endDate)
lenDF = len(deviceFiles) lenDF = len(deviceFiles)
if lenDF: if lenDF:
num_ranges = min(lenDF, listLimit or lenDF) num_ranges = min(lenDF, listLimit or lenDF)
@@ -255,22 +268,21 @@ def doGetCrosInfo():
f'available to download.') f'available to download.')
deviceFile = None deviceFile = None
if deviceFile: if deviceFile:
created = deviceFile["createTime"] created = deviceFile['createTime']
downloadfile = f'cros-logs-{deviceId}-{created}.zip' downloadfile = f'cros-logs-{deviceId}-{created}.zip'
downloadfilename = os.path.join(targetFolder, downloadfilename = os.path.join(targetFolder,
downloadfile) downloadfile)
dl_url = deviceFile['downloadUrl'] dl_url = deviceFile['downloadUrl']
_, content = cd._http.request(dl_url) _, content = cd._http.request(dl_url)
fileutils.write_file(downloadfilename, content, fileutils.write_file(downloadfilename,
content,
mode='wb', mode='wb',
continue_on_error=True) continue_on_error=True)
print(f'Downloaded: {downloadfilename}') print(f'Downloaded: {downloadfilename}')
elif downloadfile: elif downloadfile:
print('ERROR: no files to download.') print('ERROR: no files to download.')
cpuStatusReports = _filterCreateReportTime( cpuStatusReports = _filterCreateReportTime(
cros.get('cpuStatusReports', []), cros.get('cpuStatusReports', []), 'reportTime', startDate,
'reportTime',
startDate,
endDate) endDate)
lenCSR = len(cpuStatusReports) lenCSR = len(cpuStatusReports)
if lenCSR: if lenCSR:
@@ -284,8 +296,8 @@ def doGetCrosInfo():
temp_label = tempInfo['label'].strip() temp_label = tempInfo['label'].strip()
temperature = tempInfo['temperature'] temperature = tempInfo['temperature']
print(f' {temp_label}: {temperature}') print(f' {temp_label}: {temperature}')
pct_info = cpuStatusReport["cpuUtilizationPercentageInfo"] pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
util = ",".join([str(x) for x in pct_info]) util = ','.join([str(x) for x in pct_info])
print(f' cpuUtilizationPercentageInfo: {util}') print(f' cpuUtilizationPercentageInfo: {util}')
diskVolumeReports = cros.get('diskVolumeReports', []) diskVolumeReports = cros.get('diskVolumeReports', [])
lenDVR = len(diskVolumeReports) lenDVR = len(diskVolumeReports)
@@ -303,16 +315,16 @@ def doGetCrosInfo():
print(f' storageFree: {vstorage_free}') print(f' storageFree: {vstorage_free}')
print(f' storageTotal: {vstorage_total}') print(f' storageTotal: {vstorage_total}')
systemRamFreeReports = _filterCreateReportTime( systemRamFreeReports = _filterCreateReportTime(
cros.get('systemRamFreeReports', []), cros.get('systemRamFreeReports', []), 'reportTime', startDate,
'reportTime', startDate, endDate) endDate)
lenSRFR = len(systemRamFreeReports) lenSRFR = len(systemRamFreeReports)
if lenSRFR: if lenSRFR:
print(' systemRamFreeReports') print(' systemRamFreeReports')
num_ranges = min(lenSRFR, listLimit or lenSRFR) num_ranges = min(lenSRFR, listLimit or lenSRFR)
for systemRamFreeReport in systemRamFreeReports[:num_ranges]: for systemRamFreeReport in systemRamFreeReports[:num_ranges]:
report_time = systemRamFreeReport["reportTime"] report_time = systemRamFreeReport['reportTime']
free_info = systemRamFreeReport["systemRamFreeInfo"] free_info = systemRamFreeReport['systemRamFreeInfo']
free_ram = ",".join(free_info) free_ram = ','.join(free_info)
print(f' reportTime: {report_time}') print(f' reportTime: {report_time}')
print(f' systemRamFreeInfo: {free_ram}') print(f' systemRamFreeInfo: {free_ram}')
@@ -320,11 +332,15 @@ def doGetCrosInfo():
def doPrintCrosActivity(): def doPrintCrosActivity():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
todrive = False todrive = False
titles = ['deviceId', 'annotatedAssetId', titles = [
'annotatedLocation', 'serialNumber', 'orgUnitPath'] 'deviceId', 'annotatedAssetId', 'annotatedLocation', 'serialNumber',
'orgUnitPath'
]
csvRows = [] csvRows = []
fieldsList = ['deviceId', 'annotatedAssetId', fieldsList = [
'annotatedLocation', 'serialNumber', 'orgUnitPath'] 'deviceId', 'annotatedAssetId', 'annotatedLocation', 'serialNumber',
'orgUnitPath'
]
startDate = endDate = None startDate = endDate = None
selectActiveTimeRanges = selectDeviceFiles = selectRecentUsers = False selectActiveTimeRanges = selectDeviceFiles = selectRecentUsers = False
listLimit = 0 listLimit = 0
@@ -335,10 +351,10 @@ def doPrintCrosActivity():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['query', 'queries']: if myarg in ['query', 'queries']:
queries = gam.getQueries(myarg, sys.argv[i+1]) queries = gam.getQueries(myarg, sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'limittoou': elif myarg == 'limittoou':
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1]) orgUnitPath = gam.getOrgUnitItem(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'todrive': elif myarg == 'todrive':
todrive = True todrive = True
@@ -360,32 +376,35 @@ def doPrintCrosActivity():
selectRecentUsers = True selectRecentUsers = True
i += 1 i += 1
elif myarg in CROS_START_ARGUMENTS: elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i+1]) startDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg in CROS_END_ARGUMENTS: elif myarg in CROS_END_ARGUMENTS:
endDate = _getFilterDate(sys.argv[i+1]) endDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'listlimit': elif myarg == 'listlimit':
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=0) listLimit = gam.getInteger(sys.argv[i + 1], myarg, minVal=0)
i += 2 i += 2
elif myarg == 'delimiter': elif myarg == 'delimiter':
delimiter = sys.argv[i+1] delimiter = sys.argv[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam print crosactivity") 'gam print crosactivity')
if not selectActiveTimeRanges and \ if not selectActiveTimeRanges and \
not selectDeviceFiles and \ not selectDeviceFiles and \
not selectRecentUsers: not selectRecentUsers:
selectActiveTimeRanges = selectRecentUsers = True selectActiveTimeRanges = selectRecentUsers = True
if selectRecentUsers: if selectRecentUsers:
fieldsList.append('recentUsers') fieldsList.append('recentUsers')
display.add_titles_to_csv_file(['recentUsers.email', ], titles) display.add_titles_to_csv_file([
'recentUsers.email',
], titles)
if selectActiveTimeRanges: if selectActiveTimeRanges:
fieldsList.append('activeTimeRanges') fieldsList.append('activeTimeRanges')
titles_to_add = ['activeTimeRanges.date', titles_to_add = [
'activeTimeRanges.duration', 'activeTimeRanges.date', 'activeTimeRanges.duration',
'activeTimeRanges.minutes'] 'activeTimeRanges.minutes'
]
display.add_titles_to_csv_file(titles_to_add, titles) display.add_titles_to_csv_file(titles_to_add, titles)
if selectDeviceFiles: if selectDeviceFiles:
fieldsList.append('deviceFiles') fieldsList.append('deviceFiles')
@@ -395,13 +414,15 @@ def doPrintCrosActivity():
for query in queries: for query in queries:
gam.printGettingAllItems('CrOS Devices', query) gam.printGettingAllItems('CrOS Devices', query)
page_message = gapi.got_total_items_msg('CrOS Devices', '...\n') page_message = gapi.got_total_items_msg('CrOS Devices', '...\n')
all_cros = gapi.get_all_pages(cd.chromeosdevices(), 'list', all_cros = gapi.get_all_pages(cd.chromeosdevices(),
'list',
'chromeosdevices', 'chromeosdevices',
page_message=page_message, page_message=page_message,
query=query, query=query,
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
projection='FULL', projection='FULL',
fields=fields, orgUnitPath=orgUnitPath) fields=fields,
orgUnitPath=orgUnitPath)
for cros in all_cros: for cros in all_cros:
row = {} row = {}
skip_attribs = ['recentUsers', 'activeTimeRanges', 'deviceFiles'] skip_attribs = ['recentUsers', 'activeTimeRanges', 'deviceFiles']
@@ -428,9 +449,9 @@ def doPrintCrosActivity():
num_ranges = min(lenRU, listLimit or lenRU) num_ranges = min(lenRU, listLimit or lenRU)
recent_users = [] recent_users = []
for recentUser in recentUsers[:num_ranges]: for recentUser in recentUsers[:num_ranges]:
useremail = recentUser.get("email") useremail = recentUser.get('email')
if not useremail: if not useremail:
if recentUser["type"] == "USER_TYPE_UNMANAGED": if recentUser['type'] == 'USER_TYPE_UNMANAGED':
useremail = 'UnmanagedUser' useremail = 'UnmanagedUser'
else: else:
useremail = 'Unknown' useremail = 'Unknown'
@@ -439,8 +460,8 @@ def doPrintCrosActivity():
csvRows.append(row) csvRows.append(row)
if selectDeviceFiles: if selectDeviceFiles:
deviceFiles = _filterCreateReportTime( deviceFiles = _filterCreateReportTime(
cros.get('deviceFiles', []), cros.get('deviceFiles', []), 'createTime', startDate,
'createTime', startDate, endDate) endDate)
lenDF = len(deviceFiles) lenDF = len(deviceFiles)
num_ranges = min(lenDF, listLimit or lenDF) num_ranges = min(lenDF, listLimit or lenDF)
for deviceFile in deviceFiles[:num_ranges]: for deviceFile in deviceFiles[:num_ranges]:
@@ -465,6 +486,7 @@ def _checkTPMVulnerability(cros):
def doPrintCrosDevices(): def doPrintCrosDevices():
def _getSelectedLists(myarg): def _getSelectedLists(myarg):
if myarg in CROS_ACTIVE_TIME_RANGES_ARGUMENTS: if myarg in CROS_ACTIVE_TIME_RANGES_ARGUMENTS:
selectedLists['activeTimeRanges'] = True selectedLists['activeTimeRanges'] = True
@@ -485,8 +507,8 @@ def doPrintCrosDevices():
fieldsTitles = {} fieldsTitles = {}
titles = [] titles = []
csvRows = [] csvRows = []
display.add_field_to_csv_file( display.add_field_to_csv_file('deviceid', CROS_ARGUMENT_TO_PROPERTY_MAP,
'deviceid', CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) fieldsList, fieldsTitles, titles)
projection = orderBy = sortOrder = orgUnitPath = None projection = orderBy = sortOrder = orgUnitPath = None
queries = [None] queries = [None]
noLists = sortHeaders = False noLists = sortHeaders = False
@@ -497,10 +519,10 @@ def doPrintCrosDevices():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['query', 'queries']: if myarg in ['query', 'queries']:
queries = gam.getQueries(myarg, sys.argv[i+1]) queries = gam.getQueries(myarg, sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'limittoou': elif myarg == 'limittoou':
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1]) orgUnitPath = gam.getOrgUnitItem(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'todrive': elif myarg == 'todrive':
todrive = True todrive = True
@@ -510,21 +532,24 @@ def doPrintCrosDevices():
selectedLists = {} selectedLists = {}
i += 1 i += 1
elif myarg == 'listlimit': elif myarg == 'listlimit':
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=0) listLimit = gam.getInteger(sys.argv[i + 1], myarg, minVal=0)
i += 2 i += 2
elif myarg in CROS_START_ARGUMENTS: elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i+1]) startDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg in CROS_END_ARGUMENTS: elif myarg in CROS_END_ARGUMENTS:
endDate = _getFilterDate(sys.argv[i+1]) endDate = _getFilterDate(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'orderby': elif myarg == 'orderby':
orderBy = sys.argv[i+1].lower().replace('_', '') orderBy = sys.argv[i + 1].lower().replace('_', '')
validOrderBy = ['location', 'user', 'lastsync', validOrderBy = [
'notes', 'serialnumber', 'status', 'supportenddate'] 'location', 'user', 'lastsync', 'notes', 'serialnumber',
'status', 'supportenddate'
]
if orderBy not in validOrderBy: if orderBy not in validOrderBy:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('orderby',
"orderby", ", ".join(validOrderBy), orderBy) ', '.join(validOrderBy),
orderBy)
if orderBy == 'location': if orderBy == 'location':
orderBy = 'annotatedLocation' orderBy = 'annotatedLocation'
elif orderBy == 'user': elif orderBy == 'user':
@@ -559,11 +584,12 @@ def doPrintCrosDevices():
_getSelectedLists(myarg) _getSelectedLists(myarg)
i += 1 i += 1
elif myarg in CROS_ARGUMENT_TO_PROPERTY_MAP: elif myarg in CROS_ARGUMENT_TO_PROPERTY_MAP:
display.add_field_to_fields_list( display.add_field_to_fields_list(myarg,
myarg, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList) CROS_ARGUMENT_TO_PROPERTY_MAP,
fieldsList)
i += 1 i += 1
elif myarg == 'fields': elif myarg == 'fields':
fieldNameList = sys.argv[i+1] fieldNameList = sys.argv[i + 1]
for field in fieldNameList.lower().replace(',', ' ').split(): for field in fieldNameList.lower().replace(',', ' ').split():
if field in CROS_LISTS_ARGUMENTS: if field in CROS_LISTS_ARGUMENTS:
_getSelectedLists(field) _getSelectedLists(field)
@@ -571,17 +597,18 @@ def doPrintCrosDevices():
display.add_field_to_fields_list( display.add_field_to_fields_list(
field, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList) field, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList)
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(field,
field, "gam print cros fields") 'gam print cros fields')
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam print cros") controlflow.invalid_argument_exit(sys.argv[i], 'gam print cros')
if selectedLists: if selectedLists:
noLists = False noLists = False
projection = 'FULL' projection = 'FULL'
for selectList in selectedLists: for selectList in selectedLists:
display.add_field_to_fields_list( display.add_field_to_fields_list(selectList,
selectList, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList) CROS_ARGUMENT_TO_PROPERTY_MAP,
fieldsList)
if fieldsList: if fieldsList:
fieldsList.append('deviceId') fieldsList.append('deviceId')
fields = f'nextPageToken,chromeosdevices({",".join(set(fieldsList))})'.replace( fields = f'nextPageToken,chromeosdevices({",".join(set(fieldsList))})'.replace(
@@ -591,13 +618,16 @@ def doPrintCrosDevices():
for query in queries: for query in queries:
gam.printGettingAllItems('CrOS Devices', query) gam.printGettingAllItems('CrOS Devices', query)
page_message = gapi.got_total_items_msg('CrOS Devices', '...\n') page_message = gapi.got_total_items_msg('CrOS Devices', '...\n')
all_cros = gapi.get_all_pages(cd.chromeosdevices(), 'list', all_cros = gapi.get_all_pages(cd.chromeosdevices(),
'list',
'chromeosdevices', 'chromeosdevices',
page_message=page_message, query=query, page_message=page_message,
query=query,
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
projection=projection, projection=projection,
orgUnitPath=orgUnitPath, orgUnitPath=orgUnitPath,
orderBy=orderBy, sortOrder=sortOrder, orderBy=orderBy,
sortOrder=sortOrder,
fields=fields) fields=fields)
for cros in all_cros: for cros in all_cros:
_checkTPMVulnerability(cros) _checkTPMVulnerability(cros)
@@ -612,8 +642,9 @@ def doPrintCrosDevices():
tempInfos = cpuStatusReport.get('cpuTemperatureInfo', []) tempInfos = cpuStatusReport.get('cpuTemperatureInfo', [])
for tempInfo in tempInfos: for tempInfo in tempInfos:
tempInfo['label'] = tempInfo['label'].strip() tempInfo['label'] = tempInfo['label'].strip()
display.add_row_titles_to_csv_file(utils.flatten_json( display.add_row_titles_to_csv_file(
cros, listLimit=listLimit), csvRows, titles) utils.flatten_json(cros, listLimit=listLimit), csvRows,
titles)
continue continue
for cros in all_cros: for cros in all_cros:
if 'notes' in cros: if 'notes' in cros:
@@ -623,11 +654,11 @@ def doPrintCrosDevices():
cros['autoUpdateExpiration']) cros['autoUpdateExpiration'])
row = {} row = {}
for attrib in cros: for attrib in cros:
if attrib not in set(['kind', 'etag', 'tpmVersionInfo', if attrib not in set([
'recentUsers', 'activeTimeRanges', 'kind', 'etag', 'tpmVersionInfo', 'recentUsers',
'deviceFiles', 'cpuStatusReports', 'activeTimeRanges', 'deviceFiles', 'cpuStatusReports',
'diskVolumeReports', 'diskVolumeReports', 'systemRamFreeReports'
'systemRamFreeReports']): ]):
row[attrib] = cros[attrib] row[attrib] = cros[attrib]
if selectedLists.get('activeTimeRanges'): if selectedLists.get('activeTimeRanges'):
timergs = cros.get('activeTimeRanges', []) timergs = cros.get('activeTimeRanges', [])
@@ -649,8 +680,8 @@ def doPrintCrosDevices():
else: else:
cpu_reports = [] cpu_reports = []
cpuStatusReports = _filterCreateReportTime(cpu_reports, cpuStatusReports = _filterCreateReportTime(cpu_reports,
'reportTime', 'reportTime', startDate,
startDate, endDate) endDate)
if selectedLists.get('diskVolumeReports'): if selectedLists.get('diskVolumeReports'):
diskVolumeReports = cros.get('diskVolumeReports', []) diskVolumeReports = cros.get('diskVolumeReports', [])
else: else:
@@ -659,10 +690,8 @@ def doPrintCrosDevices():
ram_reports = cros.get('systemRamFreeReports', []) ram_reports = cros.get('systemRamFreeReports', [])
else: else:
ram_reports = [] ram_reports = []
systemRamFreeReports = _filterCreateReportTime(ram_reports, systemRamFreeReports = _filterCreateReportTime(
'reportTime', ram_reports, 'reportTime', startDate, endDate)
startDate,
endDate)
if noLists or (not activeTimeRanges and \ if noLists or (not activeTimeRanges and \
not recentUsers and \ not recentUsers and \
not deviceFiles and \ not deviceFiles and \
@@ -707,7 +736,7 @@ def doPrintCrosDevices():
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo', tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo',
[]) [])
for tempInfo in tempInfos: for tempInfo in tempInfos:
label = tempInfo["label"].strip() label = tempInfo['label'].strip()
base = 'cpuStatusReports.cpuTemperatureInfo.' base = 'cpuStatusReports.cpuTemperatureInfo.'
nrow[f'{base}{label}'] = tempInfo['temperature'] nrow[f'{base}{label}'] = tempInfo['temperature']
cpu_field = 'cpuUtilizationPercentageInfo' cpu_field = 'cpuUtilizationPercentageInfo'
@@ -735,16 +764,18 @@ def doPrintCrosDevices():
','.join(ram_info) ','.join(ram_info)
display.add_row_titles_to_csv_file(nrow, csvRows, titles) display.add_row_titles_to_csv_file(nrow, csvRows, titles)
if sortHeaders: if sortHeaders:
display.sort_csv_titles(['deviceId', ], titles) display.sort_csv_titles([
'deviceId',
], titles)
display.write_csv_file(csvRows, titles, 'CrOS', todrive) display.write_csv_file(csvRows, titles, 'CrOS', todrive)
def getCrOSDeviceEntity(i, cd): def getCrOSDeviceEntity(i, cd):
myarg = sys.argv[i].lower() myarg = sys.argv[i].lower()
if myarg == 'cros_sn': if myarg == 'cros_sn':
return i+2, gam.getUsersToModify('cros_sn', sys.argv[i+1]) return i + 2, gam.getUsersToModify('cros_sn', sys.argv[i + 1])
if myarg == 'query': if myarg == 'query':
return i+2, gam.getUsersToModify('crosquery', sys.argv[i+1]) return i + 2, gam.getUsersToModify('crosquery', sys.argv[i + 1])
if myarg[:6] == 'query:': if myarg[:6] == 'query:':
query = sys.argv[i][6:] query = sys.argv[i][6:]
if query[:12].lower() == 'orgunitpath:': if query[:12].lower() == 'orgunitpath:':
@@ -752,12 +783,14 @@ def getCrOSDeviceEntity(i, cd):
else: else:
kwargs = {'query': query} kwargs = {'query': query}
fields = 'nextPageToken,chromeosdevices(deviceId)' fields = 'nextPageToken,chromeosdevices(deviceId)'
devices = gapi.get_all_pages(cd.chromeosdevices(), 'list', devices = gapi.get_all_pages(cd.chromeosdevices(),
'list',
'chromeosdevices', 'chromeosdevices',
customerId=GC_Values[GC_CUSTOMER_ID], customerId=GC_Values[GC_CUSTOMER_ID],
fields=fields, **kwargs) fields=fields,
return i+1, [device['deviceId'] for device in devices] **kwargs)
return i+1, sys.argv[i].replace(',', ' ').split() return i + 1, [device['deviceId'] for device in devices]
return i + 1, sys.argv[i].replace(',', ' ').split()
def _getFilterDate(dateStr): def _getFilterDate(dateStr):
@@ -769,8 +802,8 @@ def _filterTimeRanges(activeTimeRanges, startDate, endDate):
return activeTimeRanges return activeTimeRanges
filteredTimeRanges = [] filteredTimeRanges = []
for timeRange in activeTimeRanges: for timeRange in activeTimeRanges:
activityDate = datetime.datetime.strptime( activityDate = datetime.datetime.strptime(timeRange['date'],
timeRange['date'], YYYYMMDD_FORMAT) YYYYMMDD_FORMAT)
if ((startDate is None) or \ if ((startDate is None) or \
(activityDate >= startDate)) and \ (activityDate >= startDate)) and \
((endDate is None) or \ ((endDate is None) or \

View File

@@ -9,11 +9,14 @@ from gam.gapi import reports as gapi_reports
def doGetCustomerInfo(): def doGetCustomerInfo():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
customer_info = gapi.call(cd.customers(), 'get', customer_info = gapi.call(cd.customers(),
'get',
customerKey=GC_Values[GC_CUSTOMER_ID]) customerKey=GC_Values[GC_CUSTOMER_ID])
print(f'Customer ID: {customer_info["id"]}') print(f'Customer ID: {customer_info["id"]}')
print(f'Primary Domain: {customer_info["customerDomain"]}') print(f'Primary Domain: {customer_info["customerDomain"]}')
result = gapi.call(cd.domains(), 'get', customer=customer_info['id'], result = gapi.call(cd.domains(),
'get',
customer=customer_info['id'],
domainName=customer_info['customerDomain'], domainName=customer_info['customerDomain'],
fields='verified') fields='verified')
print(f'Primary Domain Verified: {result["verified"]}') print(f'Primary Domain Verified: {result["verified"]}')
@@ -23,11 +26,13 @@ def doGetCustomerInfo():
customer_creation = customer_info['customerCreationTime'] customer_creation = customer_info['customerCreationTime']
date_format = '%Y-%m-%dT%H:%M:%S.%fZ' date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
oldest = datetime.datetime.strptime(customer_creation, date_format) oldest = datetime.datetime.strptime(customer_creation, date_format)
domains = gapi.get_items(cd.domains(), 'list', 'domains', domains = gapi.get_items(cd.domains(),
'list',
'domains',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
fields='domains(creationTime)') fields='domains(creationTime)')
for domain in domains: for domain in domains:
creation_timestamp = int(domain['creationTime'])/1000 creation_timestamp = int(domain['creationTime']) / 1000
domain_creation = datetime.datetime.fromtimestamp(creation_timestamp) domain_creation = datetime.datetime.fromtimestamp(creation_timestamp)
if domain_creation < oldest: if domain_creation < oldest:
oldest = domain_creation oldest = domain_creation
@@ -64,10 +69,12 @@ def doGetCustomerInfo():
throw_reasons = [gapi.errors.ErrorReason.INVALID] throw_reasons = [gapi.errors.ErrorReason.INVALID]
while True: while True:
try: try:
usage = gapi.get_all_pages(rep.customerUsageReports(), 'get', usage = gapi.get_all_pages(rep.customerUsageReports(),
'get',
'usageReports', 'usageReports',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
customerId=customerId, date=tryDate, customerId=customerId,
date=tryDate,
parameters=parameters) parameters=parameters)
break break
except gapi.errors.GapiInvalidError as e: except gapi.errors.GapiInvalidError as e:
@@ -92,22 +99,25 @@ def doUpdateCustomer():
if myarg in ADDRESS_FIELDS_ARGUMENT_MAP: if myarg in ADDRESS_FIELDS_ARGUMENT_MAP:
body.setdefault('postalAddress', {}) body.setdefault('postalAddress', {})
arg = ADDRESS_FIELDS_ARGUMENT_MAP[myarg] arg = ADDRESS_FIELDS_ARGUMENT_MAP[myarg]
body['postalAddress'][arg] = sys.argv[i+1] body['postalAddress'][arg] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['adminsecondaryemail', 'alternateemail']: elif myarg in ['adminsecondaryemail', 'alternateemail']:
body['alternateEmail'] = sys.argv[i+1] body['alternateEmail'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['phone', 'phonenumber']: elif myarg in ['phone', 'phonenumber']:
body['phoneNumber'] = sys.argv[i+1] body['phoneNumber'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'language': elif myarg == 'language':
body['language'] = sys.argv[i+1] body['language'] = sys.argv[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam update customer") controlflow.invalid_argument_exit(myarg, 'gam update customer')
if not body: if not body:
controlflow.system_error_exit(2, 'no arguments specified for "gam ' controlflow.system_error_exit(
2, 'no arguments specified for "gam '
'update customer"') 'update customer"')
gapi.call(cd.customers(), 'patch', customerKey=GC_Values[GC_CUSTOMER_ID], gapi.call(cd.customers(),
'patch',
customerKey=GC_Values[GC_CUSTOMER_ID],
body=body) body=body)
print('Updated customer') print('Updated customer')

View File

@@ -36,15 +36,16 @@ def printBuildings():
fieldsList.append(possible_fields[myarg]) fieldsList.append(possible_fields[myarg])
i += 1 i += 1
# Allows shorter arguments like "name" instead of "buildingname" # Allows shorter arguments like "name" instead of "buildingname"
elif 'building'+myarg in possible_fields: elif 'building' + myarg in possible_fields:
fieldsList.append(possible_fields['building'+myarg]) fieldsList.append(possible_fields['building' + myarg])
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam print buildings") 'gam print buildings')
if fields: if fields:
fields = fields % ','.join(fieldsList) fields = fields % ','.join(fieldsList)
buildings = gapi.get_all_pages(cd.resources().buildings(), 'list', buildings = gapi.get_all_pages(cd.resources().buildings(),
'list',
'buildings', 'buildings',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
fields=fields) fields=fields)
@@ -80,7 +81,7 @@ def printResourceCalendars():
todrive = True todrive = True
i += 1 i += 1
elif myarg == 'query': elif myarg == 'query':
query = sys.argv[i+1] query = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'allfields': elif myarg == 'allfields':
fieldsList = [] fieldsList = []
@@ -89,8 +90,7 @@ def printResourceCalendars():
for field in RESCAL_ALLFIELDS: for field in RESCAL_ALLFIELDS:
display.add_field_to_csv_file(field, display.add_field_to_csv_file(field,
RESCAL_ARGUMENT_TO_PROPERTY_MAP, RESCAL_ARGUMENT_TO_PROPERTY_MAP,
fieldsList, fieldsTitles, fieldsList, fieldsTitles, titles)
titles)
i += 1 i += 1
elif myarg in RESCAL_ARGUMENT_TO_PROPERTY_MAP: elif myarg in RESCAL_ARGUMENT_TO_PROPERTY_MAP:
display.add_field_to_csv_file(myarg, display.add_field_to_csv_file(myarg,
@@ -98,8 +98,8 @@ def printResourceCalendars():
fieldsList, fieldsTitles, titles) fieldsList, fieldsTitles, titles)
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam print resources") 'gam print resources')
if not fieldsList: if not fieldsList:
for field in RESCAL_DFLTFIELDS: for field in RESCAL_DFLTFIELDS:
display.add_field_to_csv_file(field, display.add_field_to_csv_file(field,
@@ -107,15 +107,19 @@ def printResourceCalendars():
fieldsList, fieldsTitles, titles) fieldsList, fieldsTitles, titles)
fields = f'nextPageToken,items({",".join(set(fieldsList))})' fields = f'nextPageToken,items({",".join(set(fieldsList))})'
if 'buildingId' in fieldsList: if 'buildingId' in fieldsList:
display.add_field_to_csv_file('buildingName', {'buildingName': [ display.add_field_to_csv_file('buildingName',
'buildingName', ]}, fieldsList, fieldsTitles, titles) {'buildingName': ['buildingName',]},
fieldsList, fieldsTitles, titles)
gam.printGettingAllItems('Resource Calendars', None) gam.printGettingAllItems('Resource Calendars', None)
page_message = gapi.got_total_items_first_last_msg('Resource Calendars') page_message = gapi.got_total_items_first_last_msg('Resource Calendars')
resources = gapi.get_all_pages(cd.resources().calendars(), 'list', resources = gapi.get_all_pages(cd.resources().calendars(),
'items', page_message=page_message, 'list',
'items',
page_message=page_message,
message_attribute='resourceId', message_attribute='resourceId',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
query=query, fields=fields) query=query,
fields=fields)
for resource in resources: for resource in resources:
if 'featureInstances' in resource: if 'featureInstances' in resource:
features = [a_feature['feature']['name'] for \ features = [a_feature['feature']['name'] for \
@@ -129,35 +133,50 @@ def printResourceCalendars():
for field in fieldsList: for field in fieldsList:
resUnit[fieldsTitles[field]] = resource.get(field, '') resUnit[fieldsTitles[field]] = resource.get(field, '')
csvRows.append(resUnit) csvRows.append(resUnit)
display.sort_csv_titles( display.sort_csv_titles(['resourceId', 'resourceName', 'resourceEmail'],
['resourceId', 'resourceName', 'resourceEmail'], titles) titles)
display.write_csv_file(csvRows, titles, 'Resources', todrive) display.write_csv_file(csvRows, titles, 'Resources', todrive)
RESCAL_DFLTFIELDS = ['id', 'name', 'email',] RESCAL_DFLTFIELDS = [
RESCAL_ALLFIELDS = ['id', 'name', 'email', 'description', 'type', 'id',
'buildingid', 'category', 'capacity', 'features', 'floor', 'name',
'floorsection', 'generatedresourcename', 'email',
'uservisibledescription',] ]
RESCAL_ALLFIELDS = [
'id',
'name',
'email',
'description',
'type',
'buildingid',
'category',
'capacity',
'features',
'floor',
'floorsection',
'generatedresourcename',
'uservisibledescription',
]
RESCAL_ARGUMENT_TO_PROPERTY_MAP = { RESCAL_ARGUMENT_TO_PROPERTY_MAP = {
'description': ['resourceDescription'], 'description': ['resourceDescription'],
'building': ['buildingId', ], 'building': ['buildingId',],
'buildingid': ['buildingId', ], 'buildingid': ['buildingId',],
'capacity': ['capacity', ], 'capacity': ['capacity',],
'category': ['resourceCategory', ], 'category': ['resourceCategory',],
'email': ['resourceEmail'], 'email': ['resourceEmail'],
'feature': ['featureInstances', ], 'feature': ['featureInstances',],
'features': ['featureInstances', ], 'features': ['featureInstances',],
'floor': ['floorName', ], 'floor': ['floorName',],
'floorname': ['floorName', ], 'floorname': ['floorName',],
'floorsection': ['floorSection', ], 'floorsection': ['floorSection',],
'generatedresourcename': ['generatedResourceName', ], 'generatedresourcename': ['generatedResourceName',],
'id': ['resourceId'], 'id': ['resourceId'],
'name': ['resourceName'], 'name': ['resourceName'],
'type': ['resourceType'], 'type': ['resourceType'],
'userdescription': ['userVisibleDescription', ], 'userdescription': ['userVisibleDescription',],
'uservisibledescription': ['userVisibleDescription', ], 'uservisibledescription': ['userVisibleDescription',],
} }
@@ -183,15 +202,15 @@ def printFeatures():
elif myarg in possible_fields: elif myarg in possible_fields:
fieldsList.append(possible_fields[myarg]) fieldsList.append(possible_fields[myarg])
i += 1 i += 1
elif 'feature'+myarg in possible_fields: elif 'feature' + myarg in possible_fields:
fieldsList.append(possible_fields['feature'+myarg]) fieldsList.append(possible_fields['feature' + myarg])
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i], 'gam print features')
sys.argv[i], "gam print features")
if fields: if fields:
fields = fields % ','.join(fieldsList) fields = fields % ','.join(fieldsList)
features = gapi.get_all_pages(cd.resources().features(), 'list', features = gapi.get_all_pages(cd.resources().features(),
'list',
'features', 'features',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
fields=fields) fields=fields)
@@ -213,57 +232,62 @@ def _getBuildingAttributes(args, body={}):
while i < len(args): while i < len(args):
myarg = args[i].lower().replace('_', '') myarg = args[i].lower().replace('_', '')
if myarg == 'id': if myarg == 'id':
body['buildingId'] = args[i+1] body['buildingId'] = args[i + 1]
i += 2 i += 2
elif myarg == 'name': elif myarg == 'name':
body['buildingName'] = args[i+1] body['buildingName'] = args[i + 1]
i += 2 i += 2
elif myarg in ['lat', 'latitude']: elif myarg in ['lat', 'latitude']:
if 'coordinates' not in body: if 'coordinates' not in body:
body['coordinates'] = {} body['coordinates'] = {}
body['coordinates']['latitude'] = args[i+1] body['coordinates']['latitude'] = args[i + 1]
i += 2 i += 2
elif myarg in ['long', 'lng', 'longitude']: elif myarg in ['long', 'lng', 'longitude']:
if 'coordinates' not in body: if 'coordinates' not in body:
body['coordinates'] = {} body['coordinates'] = {}
body['coordinates']['longitude'] = args[i+1] body['coordinates']['longitude'] = args[i + 1]
i += 2 i += 2
elif myarg == 'description': elif myarg == 'description':
body['description'] = args[i+1] body['description'] = args[i + 1]
i += 2 i += 2
elif myarg == 'floors': elif myarg == 'floors':
body['floorNames'] = args[i+1].split(',') body['floorNames'] = args[i + 1].split(',')
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(myarg,
myarg, "gam create|update building") 'gam create|update building')
return body return body
def createBuilding(): def createBuilding():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
body = {'floorNames': ['1'], body = {
'floorNames': ['1'],
'buildingId': str(uuid.uuid4()), 'buildingId': str(uuid.uuid4()),
'buildingName': sys.argv[3]} 'buildingName': sys.argv[3]
}
body = _getBuildingAttributes(sys.argv[4:], body) body = _getBuildingAttributes(sys.argv[4:], body)
print(f'Creating building {body["buildingId"]}...') print(f'Creating building {body["buildingId"]}...')
gapi.call(cd.resources().buildings(), 'insert', gapi.call(cd.resources().buildings(),
customer=GC_Values[GC_CUSTOMER_ID], body=body) 'insert',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def _makeBuildingIdNameMap(cd): def _makeBuildingIdNameMap(cd):
fields = 'nextPageToken,buildings(buildingId,buildingName)' fields = 'nextPageToken,buildings(buildingId,buildingName)'
buildings = gapi.get_all_pages(cd.resources().buildings(), 'list', buildings = gapi.get_all_pages(cd.resources().buildings(),
'list',
'buildings', 'buildings',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
fields=fields) fields=fields)
GM_Globals[GM_MAP_BUILDING_ID_TO_NAME] = {} GM_Globals[GM_MAP_BUILDING_ID_TO_NAME] = {}
GM_Globals[GM_MAP_BUILDING_NAME_TO_ID] = {} GM_Globals[GM_MAP_BUILDING_NAME_TO_ID] = {}
for building in buildings: for building in buildings:
GM_Globals[GM_MAP_BUILDING_ID_TO_NAME][building['buildingId'] GM_Globals[GM_MAP_BUILDING_ID_TO_NAME][
] = building['buildingName'] building['buildingId']] = building['buildingName']
GM_Globals[GM_MAP_BUILDING_NAME_TO_ID][building['buildingName'] GM_Globals[GM_MAP_BUILDING_NAME_TO_ID][
] = building['buildingId'] building['buildingName']] = building['buildingId']
def getBuildingByNameOrId(cd, which_building, minLen=1): def getBuildingByNameOrId(cd, which_building, minLen=1):
@@ -283,10 +307,13 @@ def getBuildingByNameOrId(cd, which_building, minLen=1):
# No exact name match, check for case insensitive name matches # No exact name match, check for case insensitive name matches
which_building_lower = which_building.lower() which_building_lower = which_building.lower()
ci_matches = [] ci_matches = []
for buildingName, buildingId in GM_Globals[GM_MAP_BUILDING_NAME_TO_ID].items(): for buildingName, buildingId in GM_Globals[
GM_MAP_BUILDING_NAME_TO_ID].items():
if buildingName.lower() == which_building_lower: if buildingName.lower() == which_building_lower:
ci_matches.append( ci_matches.append({
{'buildingName': buildingName, 'buildingId': buildingId}) 'buildingName': buildingName,
'buildingId': buildingId
})
# One match, return ID # One match, return ID
if len(ci_matches) == 1: if len(ci_matches) == 1:
return ci_matches[0]['buildingId'] return ci_matches[0]['buildingId']
@@ -323,15 +350,18 @@ def updateBuilding():
buildingId = getBuildingByNameOrId(cd, sys.argv[3]) buildingId = getBuildingByNameOrId(cd, sys.argv[3])
body = _getBuildingAttributes(sys.argv[4:]) body = _getBuildingAttributes(sys.argv[4:])
print(f'Updating building {buildingId}...') print(f'Updating building {buildingId}...')
gapi.call(cd.resources().buildings(), 'patch', gapi.call(cd.resources().buildings(),
customer=GC_Values[GC_CUSTOMER_ID], buildingId=buildingId, 'patch',
customer=GC_Values[GC_CUSTOMER_ID],
buildingId=buildingId,
body=body) body=body)
def getBuildingInfo(): def getBuildingInfo():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
buildingId = getBuildingByNameOrId(cd, sys.argv[3]) buildingId = getBuildingByNameOrId(cd, sys.argv[3])
building = gapi.call(cd.resources().buildings(), 'get', building = gapi.call(cd.resources().buildings(),
'get',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
buildingId=buildingId) buildingId=buildingId)
if 'buildingId' in building: if 'buildingId' in building:
@@ -347,8 +377,10 @@ def deleteBuilding():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
buildingId = getBuildingByNameOrId(cd, sys.argv[3]) buildingId = getBuildingByNameOrId(cd, sys.argv[3])
print(f'Deleting building {buildingId}...') print(f'Deleting building {buildingId}...')
gapi.call(cd.resources().buildings(), 'delete', gapi.call(cd.resources().buildings(),
customer=GC_Values[GC_CUSTOMER_ID], buildingId=buildingId) 'delete',
customer=GC_Values[GC_CUSTOMER_ID],
buildingId=buildingId)
def _getFeatureAttributes(args, body={}): def _getFeatureAttributes(args, body={}):
@@ -356,11 +388,11 @@ def _getFeatureAttributes(args, body={}):
while i < len(args): while i < len(args):
myarg = args[i].lower().replace('_', '') myarg = args[i].lower().replace('_', '')
if myarg == 'name': if myarg == 'name':
body['name'] = args[i+1] body['name'] = args[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(myarg,
myarg, "gam create|update feature") 'gam create|update feature')
return body return body
@@ -368,8 +400,10 @@ def createFeature():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
body = _getFeatureAttributes(sys.argv[3:]) body = _getFeatureAttributes(sys.argv[3:])
print(f'Creating feature {body["name"]}...') print(f'Creating feature {body["name"]}...')
gapi.call(cd.resources().features(), 'insert', gapi.call(cd.resources().features(),
customer=GC_Values[GC_CUSTOMER_ID], body=body) 'insert',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def updateFeature(): def updateFeature():
@@ -380,8 +414,10 @@ def updateFeature():
oldName = sys.argv[3] oldName = sys.argv[3]
body = {'newName': sys.argv[5:]} body = {'newName': sys.argv[5:]}
print(f'Updating feature {oldName}...') print(f'Updating feature {oldName}...')
gapi.call(cd.resources().features(), 'rename', gapi.call(cd.resources().features(),
customer=GC_Values[GC_CUSTOMER_ID], oldName=oldName, 'rename',
customer=GC_Values[GC_CUSTOMER_ID],
oldName=oldName,
body=body) body=body)
@@ -389,8 +425,10 @@ def deleteFeature():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
featureKey = sys.argv[3] featureKey = sys.argv[3]
print(f'Deleting feature {featureKey}...') print(f'Deleting feature {featureKey}...')
gapi.call(cd.resources().features(), 'delete', gapi.call(cd.resources().features(),
customer=GC_Values[GC_CUSTOMER_ID], featureKey=featureKey) 'delete',
customer=GC_Values[GC_CUSTOMER_ID],
featureKey=featureKey)
def _getResourceCalendarAttributes(cd, args, body={}): def _getResourceCalendarAttributes(cd, args, body={}):
@@ -398,56 +436,58 @@ def _getResourceCalendarAttributes(cd, args, body={}):
while i < len(args): while i < len(args):
myarg = args[i].lower().replace('_', '') myarg = args[i].lower().replace('_', '')
if myarg == 'name': if myarg == 'name':
body['resourceName'] = args[i+1] body['resourceName'] = args[i + 1]
i += 2 i += 2
elif myarg == 'description': elif myarg == 'description':
body['resourceDescription'] = args[i+1].replace('\\n', '\n') body['resourceDescription'] = args[i + 1].replace('\\n', '\n')
i += 2 i += 2
elif myarg == 'type': elif myarg == 'type':
body['resourceType'] = args[i+1] body['resourceType'] = args[i + 1]
i += 2 i += 2
elif myarg in ['building', 'buildingid']: elif myarg in ['building', 'buildingid']:
body['buildingId'] = getBuildingByNameOrId( body['buildingId'] = getBuildingByNameOrId(cd,
cd, args[i+1], minLen=0) args[i + 1],
minLen=0)
i += 2 i += 2
elif myarg in ['capacity']: elif myarg in ['capacity']:
body['capacity'] = gam.getInteger(args[i+1], myarg, minVal=0) body['capacity'] = gam.getInteger(args[i + 1], myarg, minVal=0)
i += 2 i += 2
elif myarg in ['feature', 'features']: elif myarg in ['feature', 'features']:
features = args[i+1].split(',') features = args[i + 1].split(',')
body['featureInstances'] = [] body['featureInstances'] = []
for feature in features: for feature in features:
instance = {'feature': {'name': feature}} instance = {'feature': {'name': feature}}
body['featureInstances'].append(instance) body['featureInstances'].append(instance)
i += 2 i += 2
elif myarg in ['floor', 'floorname']: elif myarg in ['floor', 'floorname']:
body['floorName'] = args[i+1] body['floorName'] = args[i + 1]
i += 2 i += 2
elif myarg in ['floorsection']: elif myarg in ['floorsection']:
body['floorSection'] = args[i+1] body['floorSection'] = args[i + 1]
i += 2 i += 2
elif myarg in ['category']: elif myarg in ['category']:
body['resourceCategory'] = args[i+1].upper() body['resourceCategory'] = args[i + 1].upper()
if body['resourceCategory'] == 'ROOM': if body['resourceCategory'] == 'ROOM':
body['resourceCategory'] = 'CONFERENCE_ROOM' body['resourceCategory'] = 'CONFERENCE_ROOM'
i += 2 i += 2
elif myarg in ['uservisibledescription', 'userdescription']: elif myarg in ['uservisibledescription', 'userdescription']:
body['userVisibleDescription'] = args[i+1] body['userVisibleDescription'] = args[i + 1]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(args[i],
args[i], "gam create|update resource") 'gam create|update resource')
return body return body
def createResourceCalendar(): def createResourceCalendar():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
body = {'resourceId': sys.argv[3], body = {'resourceId': sys.argv[3], 'resourceName': sys.argv[4]}
'resourceName': sys.argv[4]}
body = _getResourceCalendarAttributes(cd, sys.argv[5:], body) body = _getResourceCalendarAttributes(cd, sys.argv[5:], body)
print(f'Creating resource {body["resourceId"]}...') print(f'Creating resource {body["resourceId"]}...')
gapi.call(cd.resources().calendars(), 'insert', gapi.call(cd.resources().calendars(),
customer=GC_Values[GC_CUSTOMER_ID], body=body) 'insert',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def updateResourceCalendar(): def updateResourceCalendar():
@@ -456,16 +496,20 @@ def updateResourceCalendar():
body = _getResourceCalendarAttributes(cd, sys.argv[4:]) body = _getResourceCalendarAttributes(cd, sys.argv[4:])
# Use patch since it seems to work better. # Use patch since it seems to work better.
# update requires name to be set. # update requires name to be set.
gapi.call(cd.resources().calendars(), 'patch', gapi.call(cd.resources().calendars(),
customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId, 'patch',
body=body, fields='') customer=GC_Values[GC_CUSTOMER_ID],
calendarResourceId=resId,
body=body,
fields='')
print(f'updated resource {resId}') print(f'updated resource {resId}')
def getResourceCalendarInfo(): def getResourceCalendarInfo():
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
resId = sys.argv[3] resId = sys.argv[3]
resource = gapi.call(cd.resources().calendars(), 'get', resource = gapi.call(cd.resources().calendars(),
'get',
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
calendarResourceId=resId) calendarResourceId=resId)
if 'featureInstances' in resource: if 'featureInstances' in resource:
@@ -474,8 +518,8 @@ def getResourceCalendarInfo():
features.append(a_feature['feature']['name']) features.append(a_feature['feature']['name'])
resource['features'] = ', '.join(features) resource['features'] = ', '.join(features)
if 'buildingId' in resource: if 'buildingId' in resource:
resource['buildingName'] = getBuildingNameById( resource['buildingName'] = getBuildingNameById(cd,
cd, resource['buildingId']) resource['buildingId'])
resource['buildingId'] = f'id:{resource["buildingId"]}' resource['buildingId'] = f'id:{resource["buildingId"]}'
display.print_json(resource) display.print_json(resource)
@@ -484,5 +528,7 @@ def deleteResourceCalendar():
resId = sys.argv[3] resId = sys.argv[3]
cd = gapi_directory.buildGAPIObject() cd = gapi_directory.buildGAPIObject()
print(f'Deleting resource calendar {resId}') print(f'Deleting resource calendar {resId}')
gapi.call(cd.resources().calendars(), 'delete', gapi.call(cd.resources().calendars(),
customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId) 'delete',
customer=GC_Values[GC_CUSTOMER_ID],
calendarResourceId=resId)

View File

@@ -144,23 +144,27 @@ class ErrorReason(Enum):
# Common sets of GAPI error reasons # Common sets of GAPI error reasons
DEFAULT_RETRY_REASONS = [ DEFAULT_RETRY_REASONS = [
ErrorReason.QUOTA_EXCEEDED, ErrorReason.RATE_LIMIT_EXCEEDED, ErrorReason.QUOTA_EXCEEDED,
ErrorReason.USER_RATE_LIMIT_EXCEEDED, ErrorReason.BACKEND_ERROR, ErrorReason.RATE_LIMIT_EXCEEDED,
ErrorReason.BAD_GATEWAY, ErrorReason.GATEWAY_TIMEOUT, ErrorReason.USER_RATE_LIMIT_EXCEEDED,
ErrorReason.INTERNAL_ERROR, ErrorReason.FOUR_TWO_NINE, ErrorReason.BACKEND_ERROR,
] ErrorReason.BAD_GATEWAY,
ErrorReason.GATEWAY_TIMEOUT,
ErrorReason.INTERNAL_ERROR,
ErrorReason.FOUR_TWO_NINE,
]
GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE] GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE]
GROUP_GET_THROW_REASONS = [ GROUP_GET_THROW_REASONS = [
ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND, ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND,
ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.FORBIDDEN, ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.FORBIDDEN,
ErrorReason.BAD_REQUEST ErrorReason.BAD_REQUEST
] ]
GROUP_GET_RETRY_REASONS = [ErrorReason.INVALID, ErrorReason.SYSTEM_ERROR] GROUP_GET_RETRY_REASONS = [ErrorReason.INVALID, ErrorReason.SYSTEM_ERROR]
MEMBERS_THROW_REASONS = [ MEMBERS_THROW_REASONS = [
ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND, ErrorReason.GROUP_NOT_FOUND, ErrorReason.DOMAIN_NOT_FOUND,
ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.INVALID, ErrorReason.DOMAIN_CANNOT_USE_APIS, ErrorReason.INVALID,
ErrorReason.FORBIDDEN ErrorReason.FORBIDDEN
] ]
MEMBERS_RETRY_REASONS = [ErrorReason.SYSTEM_ERROR] MEMBERS_RETRY_REASONS = [ErrorReason.SYSTEM_ERROR]
# A map of GAPI error reasons to the corresponding GAM Python Exception # A map of GAPI error reasons to the corresponding GAM Python Exception
@@ -211,7 +215,7 @@ ERROR_REASON_TO_EXCEPTION = {
GapiServiceNotAvailableError, GapiServiceNotAvailableError,
ErrorReason.USER_NOT_FOUND: ErrorReason.USER_NOT_FOUND:
GapiUserNotFoundError, GapiUserNotFoundError,
} }
# OAuth Token Errors # OAuth Token Errors
OAUTH2_TOKEN_ERRORS = [ OAUTH2_TOKEN_ERRORS = [
@@ -233,7 +237,7 @@ OAUTH2_TOKEN_ERRORS = [
'using this method, or client not authorized for any of the scopes ' 'using this method, or client not authorized for any of the scopes '
'requested', 'requested',
'unauthorized_client: Unauthorized client or scope in request', 'unauthorized_client: Unauthorized client or scope in request',
] ]
def _create_http_error_dict(status_code, reason, message): def _create_http_error_dict(status_code, reason, message):
@@ -281,18 +285,22 @@ def get_gapi_error_detail(e,
try: try:
error = json.loads(e.content.decode(UTF8)) error = json.loads(e.content.decode(UTF8))
except ValueError: except ValueError:
error_content = e.content.decode(UTF8) if isinstance(e.content, error_content = e.content.decode(UTF8) if isinstance(
bytes) else e.content e.content, bytes) else e.content
if (e.resp['status'] == '503') and ( if (e.resp['status'] == '503') and (
error_content == 'Quota exceeded for the current request'): error_content == 'Quota exceeded for the current request'):
return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value, error_content) return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
if (e.resp['status'] == '403') and ( error_content)
error_content.startswith('Request rate higher than configured')): if (e.resp['status'] == '403') and (error_content.startswith(
return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value, error_content) 'Request rate higher than configured')):
return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
error_content)
if (e.resp['status'] == '502') and ('Bad Gateway' in error_content): if (e.resp['status'] == '502') and ('Bad Gateway' in error_content):
return (e.resp['status'], ErrorReason.BAD_GATEWAY.value, error_content) return (e.resp['status'], ErrorReason.BAD_GATEWAY.value,
error_content)
if (e.resp['status'] == '504') and ('Gateway Timeout' in error_content): if (e.resp['status'] == '504') and ('Gateway Timeout' in error_content):
return (e.resp['status'], ErrorReason.GATEWAY_TIMEOUT.value, error_content) return (e.resp['status'], ErrorReason.GATEWAY_TIMEOUT.value,
error_content)
if (e.resp['status'] == '403') and ('Invalid domain.' in error_content): if (e.resp['status'] == '403') and ('Invalid domain.' in error_content):
error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value, error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value,
'Domain not found') 'Domain not found')
@@ -324,7 +332,8 @@ def get_gapi_error_detail(e,
if error['error_description'] == 'Invalid Value': if error['error_description'] == 'Invalid Value':
message = error['error_description'] message = error['error_description']
http_status = 400 http_status = 400
error = _create_http_error_dict(400, ErrorReason.INVALID.value, message) error = _create_http_error_dict(400, ErrorReason.INVALID.value,
message)
else: else:
controlflow.system_error_exit(4, str(error)) controlflow.system_error_exit(4, str(error))
else: else:

View File

@@ -117,7 +117,8 @@ class ErrorsTest(unittest.TestCase):
self.assertEqual(message, 'Invalid Input: userId') self.assertEqual(message, 'Invalid Input: userId')
def test_get_gapi_error_extracts_invalid_member(self): def test_get_gapi_error_extracts_invalid_member(self):
err = create_simple_http_error(400, 'invalid', 'Invalid Input: memberKey') err = create_simple_http_error(400, 'invalid',
'Invalid Input: memberKey')
http_status, reason, message = errors.get_gapi_error_detail(err) http_status, reason, message = errors.get_gapi_error_detail(err)
self.assertEqual(http_status, 400) self.assertEqual(http_status, 400)
self.assertEqual(reason, errors.ErrorReason.INVALID_MEMBER.value) self.assertEqual(reason, errors.ErrorReason.INVALID_MEMBER.value)
@@ -151,8 +152,8 @@ class ErrorsTest(unittest.TestCase):
'Cyclic memberships not allowed') 'Cyclic memberships not allowed')
http_status, reason, message = errors.get_gapi_error_detail(err) http_status, reason, message = errors.get_gapi_error_detail(err)
self.assertEqual(http_status, 400) self.assertEqual(http_status, 400)
self.assertEqual(reason, self.assertEqual(
errors.ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value) reason, errors.ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value)
self.assertEqual(message, 'Cyclic memberships not allowed') self.assertEqual(message, 'Cyclic memberships not allowed')
def test_get_gapi_error_extracts_single_error_with_message(self): def test_get_gapi_error_extracts_single_error_with_message(self):

View File

@@ -42,12 +42,13 @@ REPORT_CHOICE_MAP = {
def showUsageParameters(): def showUsageParameters():
rep = buildGAPIObject() rep = buildGAPIObject()
throw_reasons = [gapi.errors.ErrorReason.INVALID, throw_reasons = [
gapi.errors.ErrorReason.BAD_REQUEST] gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST
]
todrive = False todrive = False
if len(sys.argv) == 3: if len(sys.argv) == 3:
controlflow.missing_argument_exit( controlflow.missing_argument_exit('user or customer',
'user or customer', 'report usageparameters') 'report usageparameters')
report = sys.argv[3].lower() report = sys.argv[3].lower()
titles = ['parameter'] titles = ['parameter']
if report == 'customer': if report == 'customer':
@@ -57,8 +58,8 @@ def showUsageParameters():
endpoint = rep.userUsageReport() endpoint = rep.userUsageReport()
kwargs = {'userKey': gam._getValueFromOAuth('email')} kwargs = {'userKey': gam._getValueFromOAuth('email')}
else: else:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('usageparameters',
'usageparameters', ['user', 'customer'], report) ['user', 'customer'], report)
customerId = GC_Values[GC_CUSTOMER_ID] customerId = GC_Values[GC_CUSTOMER_ID]
if customerId == MY_CUSTOMER: if customerId == MY_CUSTOMER:
customerId = None customerId = None
@@ -73,10 +74,12 @@ def showUsageParameters():
todrive = True todrive = True
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam report usageparameters") controlflow.invalid_argument_exit(sys.argv[i],
'gam report usageparameters')
while True: while True:
try: try:
response = gapi.call(endpoint, 'get', response = gapi.call(endpoint,
'get',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
date=tryDate, date=tryDate,
customerId=customerId, customerId=customerId,
@@ -87,7 +90,9 @@ def showUsageParameters():
if data.get('key') == 'application': if data.get('key') == 'application':
partial_on_thisday.append(data['value']) partial_on_thisday.append(data['value'])
if partial_apps: if partial_apps:
partial_apps = [app for app in partial_apps if app in partial_on_thisday] partial_apps = [
app for app in partial_apps if app in partial_on_thisday
]
else: else:
partial_apps = partial_on_thisday partial_apps = partial_on_thisday
for parameter in response['usageReports'][0]['parameters']: for parameter in response['usageReports'][0]['parameters']:
@@ -104,19 +109,24 @@ def showUsageParameters():
csvRows = [] csvRows = []
for parameter in all_parameters: for parameter in all_parameters:
csvRows.append({'parameter': parameter}) csvRows.append({'parameter': parameter})
display.write_csv_file( display.write_csv_file(csvRows, titles,
csvRows, titles, f'{report.capitalize()} Report Usage Parameters', todrive) f'{report.capitalize()} Report Usage Parameters',
todrive)
REPORTS_PARAMETERS_SIMPLE_TYPES = [
'intValue', 'boolValue', 'datetimeValue', 'stringValue'
]
REPORTS_PARAMETERS_SIMPLE_TYPES = ['intValue', 'boolValue', 'datetimeValue', 'stringValue']
def showUsage(): def showUsage():
rep = buildGAPIObject() rep = buildGAPIObject()
throw_reasons = [gapi.errors.ErrorReason.INVALID, throw_reasons = [
gapi.errors.ErrorReason.BAD_REQUEST] gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST
]
todrive = False todrive = False
if len(sys.argv) == 3: if len(sys.argv) == 3:
controlflow.missing_argument_exit( controlflow.missing_argument_exit('user or customer', 'report usage')
'user or customer', 'report usage')
report = sys.argv[3].lower() report = sys.argv[3].lower()
titles = ['date'] titles = ['date']
if report == 'customer': if report == 'customer':
@@ -127,8 +137,8 @@ def showUsage():
kwargs = [{'userKey': 'all'}] kwargs = [{'userKey': 'all'}]
titles.append('user') titles.append('user')
else: else:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('usage', ['user', 'customer'],
'usage', ['user', 'customer'], report) report)
customerId = GC_Values[GC_CUSTOMER_ID] customerId = GC_Values[GC_CUSTOMER_ID]
if customerId == MY_CUSTOMER: if customerId == MY_CUSTOMER:
customerId = None customerId = None
@@ -141,43 +151,47 @@ def showUsage():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'startdate': if myarg == 'startdate':
start_date = utils.get_yyyymmdd(sys.argv[i+1], returnDateTime=True) start_date = utils.get_yyyymmdd(sys.argv[i + 1],
returnDateTime=True)
i += 2 i += 2
elif myarg == 'enddate': elif myarg == 'enddate':
end_date = utils.get_yyyymmdd(sys.argv[i+1], returnDateTime=True) end_date = utils.get_yyyymmdd(sys.argv[i + 1], returnDateTime=True)
i += 2 i += 2
elif myarg == 'todrive': elif myarg == 'todrive':
todrive = True todrive = True
i += 1 i += 1
elif myarg in ['fields', 'parameters']: elif myarg in ['fields', 'parameters']:
parameters = sys.argv[i+1].split(',') parameters = sys.argv[i + 1].split(',')
i += 2 i += 2
elif myarg == 'skipdates': elif myarg == 'skipdates':
for skip in sys.argv[i+1].split(','): for skip in sys.argv[i + 1].split(','):
if skip.find(':') == -1: if skip.find(':') == -1:
skip_dates.add(utils.get_yyyymmdd(skip, returnDateTime=True)) skip_dates.add(utils.get_yyyymmdd(skip,
returnDateTime=True))
else: else:
skip_start, skip_end = skip.split(':', 1) skip_start, skip_end = skip.split(':', 1)
skip_start = utils.get_yyyymmdd(skip_start, returnDateTime=True) skip_start = utils.get_yyyymmdd(skip_start,
returnDateTime=True)
skip_end = utils.get_yyyymmdd(skip_end, returnDateTime=True) skip_end = utils.get_yyyymmdd(skip_end, returnDateTime=True)
while skip_start <= skip_end: while skip_start <= skip_end:
skip_dates.add(skip_start) skip_dates.add(skip_start)
skip_start += one_day skip_start += one_day
i += 2 i += 2
elif myarg == 'skipdaysofweek': elif myarg == 'skipdaysofweek':
skipdaynames = sys.argv[i+1].split(',') skipdaynames = sys.argv[i + 1].split(',')
dow = [d.lower() for d in calendar.day_abbr] dow = [d.lower() for d in calendar.day_abbr]
skip_day_numbers = [dow.index(d) for d in skipdaynames if d in dow] skip_day_numbers = [dow.index(d) for d in skipdaynames if d in dow]
i += 2 i += 2
elif report == 'user' and myarg in ['orgunit', 'org', 'ou']: elif report == 'user' and myarg in ['orgunit', 'org', 'ou']:
_, orgUnitId = gam.getOrgUnitId(sys.argv[i+1]) _, orgUnitId = gam.getOrgUnitId(sys.argv[i + 1])
i += 2 i += 2
elif report == 'user' and myarg in usergroup_types: elif report == 'user' and myarg in usergroup_types:
users = gam.getUsersToModify(myarg, sys.argv[i+1]) users = gam.getUsersToModify(myarg, sys.argv[i + 1])
kwargs = [{'userKey': user} for user in users] kwargs = [{'userKey': user} for user in users]
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], f'gam report usage {report}') controlflow.invalid_argument_exit(sys.argv[i],
f'gam report usage {report}')
if parameters: if parameters:
titles.extend(parameters) titles.extend(parameters)
parameters = ','.join(parameters) parameters = ','.join(parameters)
@@ -206,7 +220,8 @@ def showUsage():
try: try:
for kwarg in kwargs: for kwarg in kwargs:
try: try:
usage = gapi.get_all_pages(endpoint, 'get', usage = gapi.get_all_pages(endpoint,
'get',
'usageReports', 'usageReports',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
customerId=customerId, customerId=customerId,
@@ -250,8 +265,7 @@ def showUsage():
report_name = f'{report.capitalize()} Usage Report - {start_use_date}:{end_use_date}' report_name = f'{report.capitalize()} Usage Report - {start_use_date}:{end_use_date}'
else: else:
report_name = f'{report.capitalize()} Usage Report - {start_date}:{end_date} - No Data' report_name = f'{report.capitalize()} Usage Report - {start_date}:{end_date} - No Data'
display.write_csv_file( display.write_csv_file(csvRows, titles, report_name, todrive)
csvRows, titles, report_name, todrive)
def showReport(): def showReport():
@@ -266,11 +280,12 @@ def showReport():
showUsageParameters() showUsageParameters()
return return
valid_apps = gapi.get_enum_values_minus_unspecified( valid_apps = gapi.get_enum_values_minus_unspecified(
rep._rootDesc['resources']['activities']['methods']['list'][ rep._rootDesc['resources']['activities']['methods']['list']
'parameters']['applicationName']['enum'])+['customer', 'user'] ['parameters']['applicationName']['enum']) + ['customer', 'user']
if report not in valid_apps: if report not in valid_apps:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('report',
"report", ", ".join(sorted(valid_apps)), report) ', '.join(sorted(valid_apps)),
report)
customerId = GC_Values[GC_CUSTOMER_ID] customerId = GC_Values[GC_CUSTOMER_ID]
if customerId == MY_CUSTOMER: if customerId == MY_CUSTOMER:
customerId = None customerId = None
@@ -283,51 +298,53 @@ def showReport():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower() myarg = sys.argv[i].lower()
if myarg == 'date': if myarg == 'date':
tryDate = utils.get_yyyymmdd(sys.argv[i+1]) tryDate = utils.get_yyyymmdd(sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['orgunit', 'org', 'ou']: elif myarg in ['orgunit', 'org', 'ou']:
_, orgUnitId = gam.getOrgUnitId(sys.argv[i+1]) _, orgUnitId = gam.getOrgUnitId(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'fulldatarequired': elif myarg == 'fulldatarequired':
fullDataRequired = [] fullDataRequired = []
fdr = sys.argv[i+1].lower() fdr = sys.argv[i + 1].lower()
if fdr and fdr != 'all': if fdr and fdr != 'all':
fullDataRequired = fdr.replace(',', ' ').split() fullDataRequired = fdr.replace(',', ' ').split()
i += 2 i += 2
elif myarg == 'start': elif myarg == 'start':
startTime = utils.get_time_or_delta_from_now(sys.argv[i+1]) startTime = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'end': elif myarg == 'end':
endTime = utils.get_time_or_delta_from_now(sys.argv[i+1]) endTime = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'event': elif myarg == 'event':
eventName = sys.argv[i+1] eventName = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'user': elif myarg == 'user':
userKey = gam.normalizeEmailAddressOrUID(sys.argv[i+1]) userKey = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['filter', 'filters']: elif myarg in ['filter', 'filters']:
filters = sys.argv[i+1] filters = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['fields', 'parameters']: elif myarg in ['fields', 'parameters']:
parameters = sys.argv[i+1] parameters = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'ip': elif myarg == 'ip':
actorIpAddress = sys.argv[i+1] actorIpAddress = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'todrive': elif myarg == 'todrive':
to_drive = True to_drive = True
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam report") controlflow.invalid_argument_exit(sys.argv[i], 'gam report')
if report == 'user': if report == 'user':
while True: while True:
try: try:
if fullDataRequired is not None: if fullDataRequired is not None:
warnings = gapi.get_items(rep.userUsageReport(), 'get', warnings = gapi.get_items(rep.userUsageReport(),
'get',
'warnings', 'warnings',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
date=tryDate, userKey=userKey, date=tryDate,
userKey=userKey,
customerId=customerId, customerId=customerId,
orgUnitID=orgUnitId, orgUnitID=orgUnitId,
fields='warnings') fields='warnings')
@@ -339,11 +356,13 @@ def showReport():
if fullData == 0: if fullData == 0:
continue continue
page_message = gapi.got_total_items_msg('Users', '...\n') page_message = gapi.got_total_items_msg('Users', '...\n')
usage = gapi.get_all_pages(rep.userUsageReport(), 'get', usage = gapi.get_all_pages(rep.userUsageReport(),
'get',
'usageReports', 'usageReports',
page_message=page_message, page_message=page_message,
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
date=tryDate, userKey=userKey, date=tryDate,
userKey=userKey,
customerId=customerId, customerId=customerId,
orgUnitID=orgUnitId, orgUnitID=orgUnitId,
filters=filters, filters=filters,
@@ -359,8 +378,7 @@ def showReport():
for user_report in usage: for user_report in usage:
if 'entity' not in user_report: if 'entity' not in user_report:
continue continue
row = {'email': user_report['entity'] row = {'email': user_report['entity']['userEmail'], 'date': tryDate}
['userEmail'], 'date': tryDate}
for item in user_report.get('parameters', []): for item in user_report.get('parameters', []):
if 'name' not in item: if 'name' not in item:
continue continue
@@ -374,14 +392,15 @@ def showReport():
else: else:
row[name] = '' row[name] = ''
csvRows.append(row) csvRows.append(row)
display.write_csv_file( display.write_csv_file(csvRows, titles, f'User Reports - {tryDate}',
csvRows, titles, f'User Reports - {tryDate}', to_drive) to_drive)
elif report == 'customer': elif report == 'customer':
while True: while True:
try: try:
if fullDataRequired is not None: if fullDataRequired is not None:
warnings = gapi.get_items(rep.customerUsageReports(), warnings = gapi.get_items(rep.customerUsageReports(),
'get', 'warnings', 'get',
'warnings',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
customerId=customerId, customerId=customerId,
date=tryDate, date=tryDate,
@@ -393,7 +412,8 @@ def showReport():
sys.exit(1) sys.exit(1)
if fullData == 0: if fullData == 0:
continue continue
usage = gapi.get_all_pages(rep.customerUsageReports(), 'get', usage = gapi.get_all_pages(rep.customerUsageReports(),
'get',
'usageReports', 'usageReports',
throw_reasons=throw_reasons, throw_reasons=throw_reasons,
customerId=customerId, customerId=customerId,
@@ -442,8 +462,7 @@ def showReport():
value = ' '.join(values) value = ' '.join(values)
elif 'version_number' in subitem \ elif 'version_number' in subitem \
and 'num_devices' in subitem: and 'num_devices' in subitem:
values.append( values.append(f'{subitem["version_number"]}:'
f'{subitem["version_number"]}:'
f'{subitem["num_devices"]}') f'{subitem["num_devices"]}')
else: else:
continue continue
@@ -451,18 +470,24 @@ def showReport():
csvRows.append({'name': name, 'value': value}) csvRows.append({'name': name, 'value': value})
for app in auth_apps: # put apps at bottom for app in auth_apps: # put apps at bottom
csvRows.append(app) csvRows.append(app)
display.write_csv_file( display.write_csv_file(csvRows,
csvRows, titles, f'Customer Report - {tryDate}', todrive=to_drive) titles,
f'Customer Report - {tryDate}',
todrive=to_drive)
else: else:
page_message = gapi.got_total_items_msg('Activities', '...\n') page_message = gapi.got_total_items_msg('Activities', '...\n')
activities = gapi.get_all_pages(rep.activities(), 'list', 'items', activities = gapi.get_all_pages(rep.activities(),
'list',
'items',
page_message=page_message, page_message=page_message,
applicationName=report, applicationName=report,
userKey=userKey, userKey=userKey,
customerId=customerId, customerId=customerId,
actorIpAddress=actorIpAddress, actorIpAddress=actorIpAddress,
startTime=startTime, endTime=endTime, startTime=startTime,
eventName=eventName, filters=filters, endTime=endTime,
eventName=eventName,
filters=filters,
orgUnitID=orgUnitId) orgUnitID=orgUnitId)
if activities: if activities:
titles = ['name'] titles = ['name']
@@ -495,10 +520,11 @@ def showReport():
parts = {} parts = {}
for message in item['multiMessageValue']: for message in item['multiMessageValue']:
for mess in message['parameter']: for mess in message['parameter']:
value = mess.get('value', ' '.join( value = mess.get(
mess.get('multiValue', []))) 'value',
' '.join(mess.get('multiValue', [])))
parts[mess['name']] = parts.get( parts[mess['name']] = parts.get(
mess['name'], [])+[value] mess['name'], []) + [value]
for part, v in parts.items(): for part, v in parts.items():
if part == 'scope_name': if part == 'scope_name':
part = 'scope' part = 'scope'
@@ -513,14 +539,17 @@ def showReport():
if item not in titles: if item not in titles:
titles.append(item) titles.append(item)
csvRows.append(row) csvRows.append(row)
display.sort_csv_titles(['name', ], titles) display.sort_csv_titles([
display.write_csv_file( 'name',
csvRows, titles, f'{report.capitalize()} Activity Report', ], titles)
display.write_csv_file(csvRows, titles,
f'{report.capitalize()} Activity Report',
to_drive) to_drive)
def _adjust_date(errMsg): def _adjust_date(errMsg):
match_date = re.match('Data for dates later than (.*) is not yet ' match_date = re.match(
'Data for dates later than (.*) is not yet '
'available. Please check back later', errMsg) 'available. Please check back later', errMsg)
if not match_date: if not match_date:
match_date = re.match('Start date can not be later than (.*)', errMsg) match_date = re.match('Start date can not be later than (.*)', errMsg)

View File

@@ -16,7 +16,10 @@ def build_gapi():
return gam.buildGAPIObject('storage') return gam.buildGAPIObject('storage')
def get_cloud_storage_object(s, bucket, object_, local_file=None, def get_cloud_storage_object(s,
bucket,
object_,
local_file=None,
expectedMd5=None): expectedMd5=None):
if not local_file: if not local_file:
local_file = object_ local_file = object_
@@ -60,13 +63,19 @@ def download_bucket():
s = build_gapi() s = build_gapi()
page_message = gapi.got_total_items_msg('Files', '...') page_message = gapi.got_total_items_msg('Files', '...')
fields = 'nextPageToken,items(name,id,md5Hash)' fields = 'nextPageToken,items(name,id,md5Hash)'
objects = gapi.get_all_pages(s.objects(), 'list', 'items', objects = gapi.get_all_pages(s.objects(),
page_message=page_message, bucket=bucket, 'list',
projection='noAcl', fields=fields) 'items',
page_message=page_message,
bucket=bucket,
projection='noAcl',
fields=fields)
i = 1 i = 1
for object_ in objects: for object_ in objects:
print(f'{i}/{len(objects)}') print(f'{i}/{len(objects)}')
expectedMd5 = base64.b64decode(object_['md5Hash']).hex() expectedMd5 = base64.b64decode(object_['md5Hash']).hex()
get_cloud_storage_object( get_cloud_storage_object(s,
s, bucket, object_['name'], expectedMd5=expectedMd5) bucket,
object_['name'],
expectedMd5=expectedMd5)
i += 1 i += 1

View File

@@ -23,7 +23,8 @@ def validateCollaborators(collaboratorList, cd):
for collaborator in collaboratorList.split(','): for collaborator in collaboratorList.split(','):
collaborator_id = gam.convertEmailAddressToUID(collaborator, cd) collaborator_id = gam.convertEmailAddressToUID(collaborator, cd)
if not collaborator_id: if not collaborator_id:
controlflow.system_error_exit(4, f'failed to get a UID for ' controlflow.system_error_exit(
4, f'failed to get a UID for '
f'{collaborator}. Please make ' f'{collaborator}. Please make '
f'sure this is a real user.') f'sure this is a real user.')
collaborators.append({'email': collaborator, 'id': collaborator_id}) collaborators.append({'email': collaborator, 'id': collaborator_id})
@@ -32,7 +33,7 @@ def validateCollaborators(collaboratorList, cd):
def createMatter(): def createMatter():
v = buildGAPIObject() v = buildGAPIObject()
matter_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") matter_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
body = {'name': f'New Matter - {matter_time}'} body = {'name': f'New Matter - {matter_time}'}
collaborators = [] collaborators = []
cd = None cd = None
@@ -40,26 +41,29 @@ def createMatter():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name': if myarg == 'name':
body['name'] = sys.argv[i+1] body['name'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'description': elif myarg == 'description':
body['description'] = sys.argv[i+1] body['description'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['collaborator', 'collaborators']: elif myarg in ['collaborator', 'collaborators']:
if not cd: if not cd:
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
collaborators.extend(validateCollaborators(sys.argv[i+1], cd)) collaborators.extend(validateCollaborators(sys.argv[i + 1], cd))
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam create matter") controlflow.invalid_argument_exit(sys.argv[i], 'gam create matter')
matterId = gapi.call(v.matters(), 'create', body=body, matterId = gapi.call(v.matters(), 'create', body=body,
fields='matterId')['matterId'] fields='matterId')['matterId']
print(f'Created matter {matterId}') print(f'Created matter {matterId}')
for collaborator in collaborators: for collaborator in collaborators:
print(f' adding collaborator {collaborator["email"]}') print(f' adding collaborator {collaborator["email"]}')
body = {'matterPermission': { body = {
'matterPermission': {
'role': 'COLLABORATOR', 'role': 'COLLABORATOR',
'accountId': collaborator['id']}} 'accountId': collaborator['id']
}
}
gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body) gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body)
@@ -77,8 +81,9 @@ VAULT_SEARCH_METHODS_MAP = {
'teamdrive': 'SHARED_DRIVE', 'teamdrive': 'SHARED_DRIVE',
'teamdrives': 'SHARED_DRIVE', 'teamdrives': 'SHARED_DRIVE',
} }
VAULT_SEARCH_METHODS_LIST = ['accounts', VAULT_SEARCH_METHODS_LIST = [
'orgunit', 'shareddrives', 'rooms', 'everyone'] 'accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone'
]
def createExport(): def createExport():
@@ -98,17 +103,18 @@ def createExport():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter': if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i+1]) matterId = getMatterItem(v, sys.argv[i + 1])
body['matterId'] = matterId body['matterId'] = matterId
i += 2 i += 2
elif myarg == 'name': elif myarg == 'name':
body['name'] = sys.argv[i+1] body['name'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'corpus': elif myarg == 'corpus':
body['query']['corpus'] = sys.argv[i+1].upper() body['query']['corpus'] = sys.argv[i + 1].upper()
if body['query']['corpus'] not in allowed_corpuses: if body['query']['corpus'] not in allowed_corpuses:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('corpus',
"corpus", ", ".join(allowed_corpuses), sys.argv[i+1]) ', '.join(allowed_corpuses),
sys.argv[i + 1])
i += 2 i += 2
elif myarg in VAULT_SEARCH_METHODS_MAP: elif myarg in VAULT_SEARCH_METHODS_MAP:
if body['query'].get('searchMethod'): if body['query'].get('searchMethod'):
@@ -120,82 +126,93 @@ def createExport():
body['query']['searchMethod'] = searchMethod body['query']['searchMethod'] = searchMethod
if searchMethod == 'ACCOUNT': if searchMethod == 'ACCOUNT':
body['query']['accountInfo'] = { body['query']['accountInfo'] = {
'emails': sys.argv[i+1].split(',')} 'emails': sys.argv[i + 1].split(',')
}
i += 2 i += 2
elif searchMethod == 'ORG_UNIT': elif searchMethod == 'ORG_UNIT':
body['query']['orgUnitInfo'] = { body['query']['orgUnitInfo'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]} 'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2 i += 2
elif searchMethod == 'SHARED_DRIVE': elif searchMethod == 'SHARED_DRIVE':
body['query']['sharedDriveInfo'] = { body['query']['sharedDriveInfo'] = {
'sharedDriveIds': sys.argv[i+1].split(',')} 'sharedDriveIds': sys.argv[i + 1].split(',')
}
i += 2 i += 2
elif searchMethod == 'ROOM': elif searchMethod == 'ROOM':
body['query']['hangoutsChatInfo'] = { body['query']['hangoutsChatInfo'] = {
'roomId': sys.argv[i+1].split(',')} 'roomId': sys.argv[i + 1].split(',')
}
i += 2 i += 2
else: else:
i += 1 i += 1
elif myarg == 'scope': elif myarg == 'scope':
body['query']['dataScope'] = sys.argv[i+1].upper() body['query']['dataScope'] = sys.argv[i + 1].upper()
if body['query']['dataScope'] not in allowed_scopes: if body['query']['dataScope'] not in allowed_scopes:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('scope',
"scope", ", ".join(allowed_scopes), sys.argv[i+1]) ', '.join(allowed_scopes),
sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['terms']: elif myarg in ['terms']:
body['query']['terms'] = sys.argv[i+1] body['query']['terms'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['start', 'starttime']: elif myarg in ['start', 'starttime']:
body['query']['startTime'] = utils.get_date_zero_time_or_full_time( body['query']['startTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i+1]) sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['end', 'endtime']: elif myarg in ['end', 'endtime']:
body['query']['endTime'] = utils.get_date_zero_time_or_full_time( body['query']['endTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i+1]) sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['timezone']: elif myarg in ['timezone']:
body['query']['timeZone'] = sys.argv[i+1] body['query']['timeZone'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['excludedrafts']: elif myarg in ['excludedrafts']:
body['query']['mailOptions'] = { body['query']['mailOptions'] = {
'excludeDrafts': gam.getBoolean(sys.argv[i+1], myarg)} 'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2 i += 2
elif myarg in ['driveversiondate']: elif myarg in ['driveversiondate']:
body['query'].setdefault('driveOptions', {})['versionDate'] = \ body['query'].setdefault('driveOptions', {})['versionDate'] = \
utils.get_date_zero_time_or_full_time(sys.argv[i+1]) utils.get_date_zero_time_or_full_time(sys.argv[i+1])
i += 2 i += 2
elif myarg in ['includeshareddrives', 'includeteamdrives']: elif myarg in ['includeshareddrives', 'includeteamdrives']:
body['query'].setdefault('driveOptions', {})[ body['query'].setdefault(
'includeSharedDrives'] = gam.getBoolean(sys.argv[i+1], myarg) 'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
sys.argv[i + 1], myarg)
i += 2 i += 2
elif myarg in ['includerooms']: elif myarg in ['includerooms']:
body['query']['hangoutsChatOptions'] = { body['query']['hangoutsChatOptions'] = {
'includeRooms': gam.getBoolean(sys.argv[i+1], myarg)} 'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2 i += 2
elif myarg in ['format']: elif myarg in ['format']:
export_format = sys.argv[i+1].upper() export_format = sys.argv[i + 1].upper()
if export_format not in allowed_formats: if export_format not in allowed_formats:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('export format',
"export format", ", ".join(allowed_formats), export_format) ', '.join(allowed_formats),
export_format)
i += 2 i += 2
elif myarg in ['showconfidentialmodecontent']: elif myarg in ['showconfidentialmodecontent']:
showConfidentialModeContent = gam.getBoolean(sys.argv[i+1], myarg) showConfidentialModeContent = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2 i += 2
elif myarg in ['region']: elif myarg in ['region']:
allowed_regions = gapi.get_enum_values_minus_unspecified( allowed_regions = gapi.get_enum_values_minus_unspecified(
v._rootDesc['schemas']['ExportOptions']['properties'][ v._rootDesc['schemas']['ExportOptions']['properties']['region']
'region']['enum']) ['enum'])
body['exportOptions']['region'] = sys.argv[i+1].upper() body['exportOptions']['region'] = sys.argv[i + 1].upper()
if body['exportOptions']['region'] not in allowed_regions: if body['exportOptions']['region'] not in allowed_regions:
controlflow.expected_argument_exit("region", ", ".join( controlflow.expected_argument_exit(
allowed_regions), body['exportOptions']['region']) 'region', ', '.join(allowed_regions),
body['exportOptions']['region'])
i += 2 i += 2
elif myarg in ['includeaccessinfo']: elif myarg in ['includeaccessinfo']:
body['exportOptions'].setdefault('driveOptions', {})[ body['exportOptions'].setdefault(
'includeAccessInfo'] = gam.getBoolean(sys.argv[i+1], myarg) 'driveOptions', {})['includeAccessInfo'] = gam.getBoolean(
sys.argv[i + 1], myarg)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam create export") controlflow.invalid_argument_exit(sys.argv[i], 'gam create export')
if not matterId: if not matterId:
controlflow.system_error_exit( controlflow.system_error_exit(
3, 'you must specify a matter for the new export.') 3, 'you must specify a matter for the new export.')
@@ -207,7 +224,7 @@ def createExport():
'for the new export. Choose one of ' \ 'for the new export. Choose one of ' \
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}') f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
if 'name' not in body: if 'name' not in body:
corpus_name = body["query"]["corpus"] corpus_name = body['query']['corpus']
corpus_date = datetime.datetime.now() corpus_date = datetime.datetime.now()
body['name'] = f'GAM {corpus_name} export - {corpus_date}' body['name'] = f'GAM {corpus_name} export - {corpus_date}'
options_field = None options_field = None
@@ -223,8 +240,10 @@ def createExport():
if showConfidentialModeContent is not None: if showConfidentialModeContent is not None:
body['exportOptions'][options_field][ body['exportOptions'][options_field][
'showConfidentialModeContent'] = showConfidentialModeContent 'showConfidentialModeContent'] = showConfidentialModeContent
results = gapi.call(v.matters().exports(), 'create', results = gapi.call(v.matters().exports(),
matterId=matterId, body=body) 'create',
matterId=matterId,
body=body)
print(f'Created export {results["id"]}') print(f'Created export {results["id"]}')
display.print_json(results) display.print_json(results)
@@ -234,16 +253,20 @@ def deleteExport():
matterId = getMatterItem(v, sys.argv[3]) matterId = getMatterItem(v, sys.argv[3])
exportId = convertExportNameToID(v, sys.argv[4], matterId) exportId = convertExportNameToID(v, sys.argv[4], matterId)
print(f'Deleting export {sys.argv[4]} / {exportId}') print(f'Deleting export {sys.argv[4]} / {exportId}')
gapi.call(v.matters().exports(), 'delete', gapi.call(v.matters().exports(),
matterId=matterId, exportId=exportId) 'delete',
matterId=matterId,
exportId=exportId)
def getExportInfo(): def getExportInfo():
v = buildGAPIObject() v = buildGAPIObject()
matterId = getMatterItem(v, sys.argv[3]) matterId = getMatterItem(v, sys.argv[3])
exportId = convertExportNameToID(v, sys.argv[4], matterId) exportId = convertExportNameToID(v, sys.argv[4], matterId)
export = gapi.call(v.matters().exports(), 'get', export = gapi.call(v.matters().exports(),
matterId=matterId, exportId=exportId) 'get',
matterId=matterId,
exportId=exportId)
display.print_json(export) display.print_json(export)
@@ -261,35 +284,37 @@ def createHold():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name': if myarg == 'name':
body['name'] = sys.argv[i+1] body['name'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'query': elif myarg == 'query':
query = sys.argv[i+1] query = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'corpus': elif myarg == 'corpus':
body['corpus'] = sys.argv[i+1].upper() body['corpus'] = sys.argv[i + 1].upper()
if body['corpus'] not in allowed_corpuses: if body['corpus'] not in allowed_corpuses:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('corpus',
"corpus", ", ".join(allowed_corpuses), sys.argv[i+1]) ', '.join(allowed_corpuses),
sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['accounts', 'users', 'groups']: elif myarg in ['accounts', 'users', 'groups']:
accounts = sys.argv[i+1].split(',') accounts = sys.argv[i + 1].split(',')
i += 2 i += 2
elif myarg in ['orgunit', 'ou']: elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = { body['orgUnit'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]} 'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2 i += 2
elif myarg in ['start', 'starttime']: elif myarg in ['start', 'starttime']:
start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) start_time = utils.get_date_zero_time_or_full_time(sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['end', 'endtime']: elif myarg in ['end', 'endtime']:
end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) end_time = utils.get_date_zero_time_or_full_time(sys.argv[i + 1])
i += 2 i += 2
elif myarg == 'matter': elif myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i+1]) matterId = getMatterItem(v, sys.argv[i + 1])
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam create hold") controlflow.invalid_argument_exit(sys.argv[i], 'gam create hold')
if not matterId: if not matterId:
controlflow.system_error_exit( controlflow.system_error_exit(
3, 'you must specify a matter for the new hold.') 3, 'you must specify a matter for the new hold.')
@@ -322,13 +347,15 @@ def createHold():
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
account_type = 'group' if body['corpus'] == 'GROUPS' else 'user' account_type = 'group' if body['corpus'] == 'GROUPS' else 'user'
for account in accounts: for account in accounts:
body['accounts'].append( body['accounts'].append({
{'accountId': gam.convertEmailAddressToUID(account, 'accountId':
cd, gam.convertEmailAddressToUID(account, cd, account_type)
account_type)} })
) holdId = gapi.call(v.matters().holds(),
holdId = gapi.call(v.matters().holds(), 'create', 'create',
matterId=matterId, body=body, fields='holdId') matterId=matterId,
body=body,
fields='holdId')
print(f'Created hold {holdId["holdId"]}') print(f'Created hold {holdId["holdId"]}')
@@ -340,11 +367,11 @@ def deleteHold():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter': if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i+1]) matterId = getMatterItem(v, sys.argv[i + 1])
holdId = convertHoldNameToID(v, hold, matterId) holdId = convertHoldNameToID(v, hold, matterId)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam delete hold") controlflow.invalid_argument_exit(myarg, 'gam delete hold')
if not matterId: if not matterId:
controlflow.system_error_exit( controlflow.system_error_exit(
3, 'you must specify a matter for the hold.') 3, 'you must specify a matter for the hold.')
@@ -360,23 +387,24 @@ def getHoldInfo():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter': if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i+1]) matterId = getMatterItem(v, sys.argv[i + 1])
holdId = convertHoldNameToID(v, hold, matterId) holdId = convertHoldNameToID(v, hold, matterId)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam info hold") controlflow.invalid_argument_exit(myarg, 'gam info hold')
if not matterId: if not matterId:
controlflow.system_error_exit( controlflow.system_error_exit(
3, 'you must specify a matter for the hold.') 3, 'you must specify a matter for the hold.')
results = gapi.call(v.matters().holds(), 'get', results = gapi.call(v.matters().holds(),
matterId=matterId, holdId=holdId) 'get',
matterId=matterId,
holdId=holdId)
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
if 'accounts' in results: if 'accounts' in results:
account_type = 'group' if results['corpus'] == 'GROUPS' else 'user' account_type = 'group' if results['corpus'] == 'GROUPS' else 'user'
for i in range(0, len(results['accounts'])): for i in range(0, len(results['accounts'])):
uid = f'uid:{results["accounts"][i]["accountId"]}' uid = f'uid:{results["accounts"][i]["accountId"]}'
acct_email = gam.convertUIDtoEmailAddress( acct_email = gam.convertUIDtoEmailAddress(uid, cd, [account_type])
uid, cd, [account_type])
results['accounts'][i]['email'] = acct_email results['accounts'][i]['email'] = acct_email
if 'orgUnit' in results: if 'orgUnit' in results:
results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo( results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo(
@@ -390,12 +418,16 @@ def convertExportNameToID(v, nameOrID, matterId):
if cg: if cg:
return cg.group(1) return cg.group(1)
fields = 'exports(id,name),nextPageToken' fields = 'exports(id,name),nextPageToken'
exports = gapi.get_all_pages(v.matters().exports( exports = gapi.get_all_pages(v.matters().exports(),
), 'list', 'exports', matterId=matterId, fields=fields) 'list',
'exports',
matterId=matterId,
fields=fields)
for export in exports: for export in exports:
if export['name'].lower() == nameOrID: if export['name'].lower() == nameOrID:
return export['id'] return export['id']
controlflow.system_error_exit(4, f'could not find export name {nameOrID} ' controlflow.system_error_exit(
4, f'could not find export name {nameOrID} '
f'in matter {matterId}') f'in matter {matterId}')
@@ -405,12 +437,16 @@ def convertHoldNameToID(v, nameOrID, matterId):
if cg: if cg:
return cg.group(1) return cg.group(1)
fields = 'holds(holdId,name),nextPageToken' fields = 'holds(holdId,name),nextPageToken'
holds = gapi.get_all_pages(v.matters().holds( holds = gapi.get_all_pages(v.matters().holds(),
), 'list', 'holds', matterId=matterId, fields=fields) 'list',
'holds',
matterId=matterId,
fields=fields)
for hold in holds: for hold in holds:
if hold['name'].lower() == nameOrID: if hold['name'].lower() == nameOrID:
return hold['holdId'] return hold['holdId']
controlflow.system_error_exit(4, f'could not find hold name {nameOrID} ' controlflow.system_error_exit(
4, f'could not find hold name {nameOrID} '
f'in matter {matterId}') f'in matter {matterId}')
@@ -420,8 +456,11 @@ def convertMatterNameToID(v, nameOrID):
if cg: if cg:
return cg.group(1) return cg.group(1)
fields = 'matters(matterId,name),nextPageToken' fields = 'matters(matterId,name),nextPageToken'
matters = gapi.get_all_pages(v.matters( matters = gapi.get_all_pages(v.matters(),
), 'list', 'matters', view='BASIC', fields=fields) 'list',
'matters',
view='BASIC',
fields=fields)
for matter in matters: for matter in matters:
if matter['name'].lower() == nameOrID: if matter['name'].lower() == nameOrID:
return matter['matterId'] return matter['matterId']
@@ -449,36 +488,41 @@ def updateHold():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter': if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i+1]) matterId = getMatterItem(v, sys.argv[i + 1])
holdId = convertHoldNameToID(v, hold, matterId) holdId = convertHoldNameToID(v, hold, matterId)
i += 2 i += 2
elif myarg == 'query': elif myarg == 'query':
query = sys.argv[i+1] query = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['orgunit', 'ou']: elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = {'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]} body['orgUnit'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2 i += 2
elif myarg in ['start', 'starttime']: elif myarg in ['start', 'starttime']:
start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) start_time = utils.get_date_zero_time_or_full_time(sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['end', 'endtime']: elif myarg in ['end', 'endtime']:
end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) end_time = utils.get_date_zero_time_or_full_time(sys.argv[i + 1])
i += 2 i += 2
elif myarg in ['addusers', 'addaccounts', 'addgroups']: elif myarg in ['addusers', 'addaccounts', 'addgroups']:
add_accounts = sys.argv[i+1].split(',') add_accounts = sys.argv[i + 1].split(',')
i += 2 i += 2
elif myarg in ['removeusers', 'removeaccounts', 'removegroups']: elif myarg in ['removeusers', 'removeaccounts', 'removegroups']:
del_accounts = sys.argv[i+1].split(',') del_accounts = sys.argv[i + 1].split(',')
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam update hold") controlflow.invalid_argument_exit(myarg, 'gam update hold')
if not matterId: if not matterId:
controlflow.system_error_exit( controlflow.system_error_exit(
3, 'you must specify a matter for the hold.') 3, 'you must specify a matter for the hold.')
if query or start_time or end_time or body.get('orgUnit'): if query or start_time or end_time or body.get('orgUnit'):
fields = 'corpus,query,orgUnit' fields = 'corpus,query,orgUnit'
old_body = gapi.call(v.matters().holds( old_body = gapi.call(v.matters().holds(),
), 'get', matterId=matterId, holdId=holdId, fields=fields) 'get',
matterId=matterId,
holdId=holdId,
fields=fields)
body['query'] = old_body['query'] body['query'] = old_body['query']
body['corpus'] = old_body['corpus'] body['corpus'] = old_body['corpus']
if 'orgUnit' in old_body and 'orgUnit' not in body: if 'orgUnit' in old_body and 'orgUnit' not in body:
@@ -502,20 +546,29 @@ def updateHold():
body['query'][query_type]['endTime'] = end_time body['query'][query_type]['endTime'] = end_time
if body: if body:
print(f'Updating hold {hold} / {holdId}') print(f'Updating hold {hold} / {holdId}')
gapi.call(v.matters().holds(), 'update', gapi.call(v.matters().holds(),
matterId=matterId, holdId=holdId, body=body) 'update',
matterId=matterId,
holdId=holdId,
body=body)
if add_accounts or del_accounts: if add_accounts or del_accounts:
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
for account in add_accounts: for account in add_accounts:
print(f'adding {account} to hold.') print(f'adding {account} to hold.')
add_body = {'accountId': gam.convertEmailAddressToUID(account, cd)} add_body = {'accountId': gam.convertEmailAddressToUID(account, cd)}
gapi.call(v.matters().holds().accounts(), 'create', gapi.call(v.matters().holds().accounts(),
matterId=matterId, holdId=holdId, body=add_body) 'create',
matterId=matterId,
holdId=holdId,
body=add_body)
for account in del_accounts: for account in del_accounts:
print(f'removing {account} from hold.') print(f'removing {account} from hold.')
accountId = gam.convertEmailAddressToUID(account, cd) accountId = gam.convertEmailAddressToUID(account, cd)
gapi.call(v.matters().holds().accounts(), 'delete', gapi.call(v.matters().holds().accounts(),
matterId=matterId, holdId=holdId, accountId=accountId) 'delete',
matterId=matterId,
holdId=holdId,
accountId=accountId)
def updateMatter(action=None): def updateMatter(action=None):
@@ -530,30 +583,30 @@ def updateMatter(action=None):
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'action': if myarg == 'action':
action = sys.argv[i+1].lower() action = sys.argv[i + 1].lower()
if action not in VAULT_MATTER_ACTIONS: if action not in VAULT_MATTER_ACTIONS:
controlflow.system_error_exit(3, f'allowed actions are ' \ controlflow.system_error_exit(3, f'allowed actions are ' \
f'{", ".join(VAULT_MATTER_ACTIONS)}, got {action}') f'{", ".join(VAULT_MATTER_ACTIONS)}, got {action}')
i += 2 i += 2
elif myarg == 'name': elif myarg == 'name':
body['name'] = sys.argv[i+1] body['name'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg == 'description': elif myarg == 'description':
body['description'] = sys.argv[i+1] body['description'] = sys.argv[i + 1]
i += 2 i += 2
elif myarg in ['addcollaborator', 'addcollaborators']: elif myarg in ['addcollaborator', 'addcollaborators']:
if not cd: if not cd:
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
add_collaborators.extend(validateCollaborators(sys.argv[i+1], cd)) add_collaborators.extend(validateCollaborators(sys.argv[i + 1], cd))
i += 2 i += 2
elif myarg in ['removecollaborator', 'removecollaborators']: elif myarg in ['removecollaborator', 'removecollaborators']:
if not cd: if not cd:
cd = gam.buildGAPIObject('directory') cd = gam.buildGAPIObject('directory')
remove_collaborators.extend( remove_collaborators.extend(
validateCollaborators(sys.argv[i+1], cd)) validateCollaborators(sys.argv[i + 1], cd))
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(sys.argv[i], "gam update matter") controlflow.invalid_argument_exit(sys.argv[i], 'gam update matter')
if action == 'delete': if action == 'delete':
action_kwargs = {} action_kwargs = {}
if body: if body:
@@ -561,8 +614,10 @@ def updateMatter(action=None):
if 'name' not in body or 'description' not in body: if 'name' not in body or 'description' not in body:
# bah, API requires name/description to be sent # bah, API requires name/description to be sent
# on update even when it's not changing # on update even when it's not changing
result = gapi.call(v.matters(), 'get', result = gapi.call(v.matters(),
matterId=matterId, view='BASIC') 'get',
matterId=matterId,
view='BASIC')
body.setdefault('name', result['name']) body.setdefault('name', result['name'])
body.setdefault('description', result.get('description')) body.setdefault('description', result.get('description'))
gapi.call(v.matters(), 'update', body=body, matterId=matterId) gapi.call(v.matters(), 'update', body=body, matterId=matterId)
@@ -571,12 +626,18 @@ def updateMatter(action=None):
gapi.call(v.matters(), action, matterId=matterId, **action_kwargs) gapi.call(v.matters(), action, matterId=matterId, **action_kwargs)
for collaborator in add_collaborators: for collaborator in add_collaborators:
print(f' adding collaborator {collaborator["email"]}') print(f' adding collaborator {collaborator["email"]}')
body = {'matterPermission': {'role': 'COLLABORATOR', body = {
'accountId': collaborator['id']}} 'matterPermission': {
'role': 'COLLABORATOR',
'accountId': collaborator['id']
}
}
gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body) gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body)
for collaborator in remove_collaborators: for collaborator in remove_collaborators:
print(f' removing collaborator {collaborator["email"]}') print(f' removing collaborator {collaborator["email"]}')
gapi.call(v.matters(), 'removePermissions', matterId=matterId, gapi.call(v.matters(),
'removePermissions',
matterId=matterId,
body={'accountId': collaborator['id']}) body={'accountId': collaborator['id']})
@@ -605,7 +666,7 @@ def downloadExport():
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'targetfolder': if myarg == 'targetfolder':
targetFolder = os.path.expanduser(sys.argv[i+1]) targetFolder = os.path.expanduser(sys.argv[i + 1])
if not os.path.isdir(targetFolder): if not os.path.isdir(targetFolder):
os.makedirs(targetFolder) os.makedirs(targetFolder)
i += 2 i += 2
@@ -616,10 +677,12 @@ def downloadExport():
extractFiles = False extractFiles = False
i += 1 i += 1
else: else:
controlflow.invalid_argument_exit( controlflow.invalid_argument_exit(sys.argv[i],
sys.argv[i], "gam download export") 'gam download export')
export = gapi.call(v.matters().exports(), 'get', export = gapi.call(v.matters().exports(),
matterId=matterId, exportId=exportId) 'get',
matterId=matterId,
exportId=exportId)
for s_file in export['cloudStorageSink']['files']: for s_file in export['cloudStorageSink']['files']:
bucket = s_file['bucketName'] bucket = s_file['bucketName']
s_object = s_file['objectName'] s_object = s_file['objectName']
@@ -631,8 +694,8 @@ def downloadExport():
done = False done = False
while not done: while not done:
status, done = downloader.next_chunk() status, done = downloader.next_chunk()
sys.stdout.write( sys.stdout.write(' Downloaded: {0:>7.2%}\r'.format(
' Downloaded: {0:>7.2%}\r'.format(status.progress())) status.progress()))
sys.stdout.flush() sys.stdout.flush()
sys.stdout.write('\n Download complete. Flushing to disk...\n') sys.stdout.write('\n Download complete. Flushing to disk...\n')
fileutils.close_file(f, True) fileutils.close_file(f, True)
@@ -665,23 +728,26 @@ def printMatters():
i += 1 i += 1
elif myarg == 'matterstate': elif myarg == 'matterstate':
valid_states = gapi.get_enum_values_minus_unspecified( valid_states = gapi.get_enum_values_minus_unspecified(
v._rootDesc['schemas']['Matter']['properties']['state'][ v._rootDesc['schemas']['Matter']['properties']['state']['enum'])
'enum']) state = sys.argv[i + 1].upper()
state = sys.argv[i+1].upper()
if state not in valid_states: if state not in valid_states:
controlflow.expected_argument_exit( controlflow.expected_argument_exit('state',
'state', ', '.join(valid_states), state) ', '.join(valid_states),
state)
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam print matters") controlflow.invalid_argument_exit(myarg, 'gam print matters')
gam.printGettingAllItems('Vault Matters', None) gam.printGettingAllItems('Vault Matters', None)
page_message = gapi.got_total_items_msg('Vault Matters', '...\n') page_message = gapi.got_total_items_msg('Vault Matters', '...\n')
matters = gapi.get_all_pages( matters = gapi.get_all_pages(v.matters(),
v.matters(), 'list', 'matters', page_message=page_message, view=view, 'list',
'matters',
page_message=page_message,
view=view,
state=state) state=state)
for matter in matters: for matter in matters:
display.add_row_titles_to_csv_file( display.add_row_titles_to_csv_file(utils.flatten_json(matter), csvRows,
utils.flatten_json(matter), csvRows, titles) titles)
display.sort_csv_titles(initialTitles, titles) display.sort_csv_titles(initialTitles, titles)
display.write_csv_file(csvRows, titles, 'Vault Matters', todrive) display.write_csv_file(csvRows, titles, 'Vault Matters', todrive)
@@ -701,14 +767,18 @@ def printExports():
todrive = True todrive = True
i += 1 i += 1
elif myarg in ['matter', 'matters']: elif myarg in ['matter', 'matters']:
matters = sys.argv[i+1].split(',') matters = sys.argv[i + 1].split(',')
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam print exports") controlflow.invalid_argument_exit(myarg, 'gam print exports')
if not matters: if not matters:
fields = 'matters(matterId),nextPageToken' fields = 'matters(matterId),nextPageToken'
matters_results = gapi.get_all_pages(v.matters( matters_results = gapi.get_all_pages(v.matters(),
), 'list', 'matters', view='BASIC', state='OPEN', fields=fields) 'list',
'matters',
view='BASIC',
state='OPEN',
fields=fields)
for matter in matters_results: for matter in matters_results:
matterIds.append(matter['matterId']) matterIds.append(matter['matterId'])
else: else:
@@ -716,11 +786,14 @@ def printExports():
matterIds.append(getMatterItem(v, matter)) matterIds.append(getMatterItem(v, matter))
for matterId in matterIds: for matterId in matterIds:
sys.stderr.write(f'Retrieving exports for matter {matterId}\n') sys.stderr.write(f'Retrieving exports for matter {matterId}\n')
exports = gapi.get_all_pages( exports = gapi.get_all_pages(v.matters().exports(),
v.matters().exports(), 'list', 'exports', matterId=matterId) 'list',
'exports',
matterId=matterId)
for export in exports: for export in exports:
display.add_row_titles_to_csv_file(utils.flatten_json( display.add_row_titles_to_csv_file(
export, flattened={'matterId': matterId}), csvRows, titles) utils.flatten_json(export, flattened={'matterId': matterId}),
csvRows, titles)
display.sort_csv_titles(initialTitles, titles) display.sort_csv_titles(initialTitles, titles)
display.write_csv_file(csvRows, titles, 'Vault Exports', todrive) display.write_csv_file(csvRows, titles, 'Vault Exports', todrive)
@@ -740,14 +813,18 @@ def printHolds():
todrive = True todrive = True
i += 1 i += 1
elif myarg in ['matter', 'matters']: elif myarg in ['matter', 'matters']:
matters = sys.argv[i+1].split(',') matters = sys.argv[i + 1].split(',')
i += 2 i += 2
else: else:
controlflow.invalid_argument_exit(myarg, "gam print holds") controlflow.invalid_argument_exit(myarg, 'gam print holds')
if not matters: if not matters:
fields = 'matters(matterId),nextPageToken' fields = 'matters(matterId),nextPageToken'
matters_results = gapi.get_all_pages(v.matters( matters_results = gapi.get_all_pages(v.matters(),
), 'list', 'matters', view='BASIC', state='OPEN', fields=fields) 'list',
'matters',
view='BASIC',
state='OPEN',
fields=fields)
for matter in matters_results: for matter in matters_results:
matterIds.append(matter['matterId']) matterIds.append(matter['matterId'])
else: else:
@@ -755,10 +832,13 @@ def printHolds():
matterIds.append(getMatterItem(v, matter)) matterIds.append(getMatterItem(v, matter))
for matterId in matterIds: for matterId in matterIds:
sys.stderr.write(f'Retrieving holds for matter {matterId}\n') sys.stderr.write(f'Retrieving holds for matter {matterId}\n')
holds = gapi.get_all_pages( holds = gapi.get_all_pages(v.matters().holds(),
v.matters().holds(), 'list', 'holds', matterId=matterId) 'list',
'holds',
matterId=matterId)
for hold in holds: for hold in holds:
display.add_row_titles_to_csv_file(utils.flatten_json( display.add_row_titles_to_csv_file(
hold, flattened={'matterId': matterId}), csvRows, titles) utils.flatten_json(hold, flattened={'matterId': matterId}),
csvRows, titles)
display.sort_csv_titles(initialTitles, titles) display.sort_csv_titles(initialTitles, titles)
display.write_csv_file(csvRows, titles, 'Vault Holds', todrive) display.write_csv_file(csvRows, titles, 'Vault Holds', todrive)

View File

@@ -31,8 +31,7 @@ def create_http(cache=None,
GC_TLS_MIN_VERSION) GC_TLS_MIN_VERSION)
tls_maximum_version = override_max_tls if override_max_tls else GC_Values.get( tls_maximum_version = override_max_tls if override_max_tls else GC_Values.get(
GC_TLS_MAX_VERSION) GC_TLS_MAX_VERSION)
httpObj = httplib2.Http( httpObj = httplib2.Http(ca_certs=GC_Values.get(GC_CA_FILE),
ca_certs=GC_Values.get(GC_CA_FILE),
tls_maximum_version=tls_maximum_version, tls_maximum_version=tls_maximum_version,
tls_minimum_version=tls_minimum_version, tls_minimum_version=tls_minimum_version,
cache=cache, cache=cache,

View File

@@ -25,7 +25,8 @@ class CreateHttpTest(unittest.TestCase):
transport.GC_Values[transport.GC_TLS_MIN_VERSION]) transport.GC_Values[transport.GC_TLS_MIN_VERSION])
self.assertEqual(http.tls_maximum_version, self.assertEqual(http.tls_maximum_version,
transport.GC_Values[transport.GC_TLS_MAX_VERSION]) transport.GC_Values[transport.GC_TLS_MAX_VERSION])
self.assertEqual(http.ca_certs, transport.GC_Values[transport.GC_CA_FILE]) self.assertEqual(http.ca_certs,
transport.GC_Values[transport.GC_CA_FILE])
def test_create_http_sets_tls_min_version(self): def test_create_http_sets_tls_min_version(self):
http = transport.create_http(override_min_tls='TLSv1_1') http = transport.create_http(override_min_tls='TLSv1_1')
@@ -90,7 +91,9 @@ class TransportTest(unittest.TestCase):
def test_request_call_forces_user_agent_no_agent_in_headers(self): def test_request_call_forces_user_agent_no_agent_in_headers(self):
request = transport.Request(self.mock_http) request = transport.Request(self.mock_http)
fake_request_headers = {'some-header-thats-not-a-user-agent': 'someData'} fake_request_headers = {
'some-header-thats-not-a-user-agent': 'someData'
}
request(self.test_uri, headers=fake_request_headers) request(self.test_uri, headers=fake_request_headers)
final_headers = self.mock_http.request.call_args[1]['headers'] final_headers = self.mock_http.request.call_args[1]['headers']
@@ -119,31 +122,34 @@ class TransportTest(unittest.TestCase):
self.assertIn('user-agent', final_headers) self.assertIn('user-agent', final_headers)
self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent']) self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent'])
# Make sure the header wasn't duplicated # Make sure the header wasn't duplicated
self.assertEqual( self.assertEqual(len(transport.GAM_USER_AGENT),
len(transport.GAM_USER_AGENT), len(final_headers['user-agent'])) len(final_headers['user-agent']))
def test_authorizedhttp_is_google_auth_httplib2_compatible(self): def test_authorizedhttp_is_google_auth_httplib2_compatible(self):
http = transport.AuthorizedHttp(self.mock_credentials) http = transport.AuthorizedHttp(self.mock_credentials)
self.assertIsInstance(http, google_auth_httplib2.AuthorizedHttp) self.assertIsInstance(http, google_auth_httplib2.AuthorizedHttp)
def test_authorizedhttp_request_returns_response_content(self): def test_authorizedhttp_request_returns_response_content(self):
http = transport.AuthorizedHttp(self.mock_credentials, http=self.mock_http) http = transport.AuthorizedHttp(self.mock_credentials,
http=self.mock_http)
response, content = http.request(self.test_uri) response, content = http.request(self.test_uri)
self.assertEqual(self.mock_response, response) self.assertEqual(self.mock_response, response)
self.assertEqual(self.mock_content, content) self.assertEqual(self.mock_content, content)
def test_authorizedhttp_request_forces_user_agent_no_provided_headers(self): def test_authorizedhttp_request_forces_user_agent_no_provided_headers(self):
authorized_http = transport.AuthorizedHttp( authorized_http = transport.AuthorizedHttp(self.mock_credentials,
self.mock_credentials, http=self.mock_http) http=self.mock_http)
authorized_http.request(self.test_uri) authorized_http.request(self.test_uri)
headers = self.mock_http.request.call_args[1]['headers'] headers = self.mock_http.request.call_args[1]['headers']
self.assertIn('user-agent', headers) self.assertIn('user-agent', headers)
self.assertIn(transport.GAM_USER_AGENT, headers['user-agent']) self.assertIn(transport.GAM_USER_AGENT, headers['user-agent'])
def test_authorizedhttp_request_forces_user_agent_no_agent_in_headers(self): def test_authorizedhttp_request_forces_user_agent_no_agent_in_headers(self):
authorized_http = transport.AuthorizedHttp( authorized_http = transport.AuthorizedHttp(self.mock_credentials,
self.mock_credentials, http=self.mock_http) http=self.mock_http)
fake_request_headers = {'some-header-thats-not-a-user-agent': 'someData'} fake_request_headers = {
'some-header-thats-not-a-user-agent': 'someData'
}
authorized_http.request(self.test_uri, headers=fake_request_headers) authorized_http.request(self.test_uri, headers=fake_request_headers)
final_headers = self.mock_http.request.call_args[1]['headers'] final_headers = self.mock_http.request.call_args[1]['headers']
@@ -155,8 +161,8 @@ class TransportTest(unittest.TestCase):
def test_authorizedhttp_request_forces_user_agent_with_another_agent_in_headers( def test_authorizedhttp_request_forces_user_agent_with_another_agent_in_headers(
self): self):
authorized_http = transport.AuthorizedHttp( authorized_http = transport.AuthorizedHttp(self.mock_credentials,
self.mock_credentials, http=self.mock_http) http=self.mock_http)
headers_with_user_agent = {'user-agent': 'existing-user-agent'} headers_with_user_agent = {'user-agent': 'existing-user-agent'}
authorized_http.request(self.test_uri, headers=headers_with_user_agent) authorized_http.request(self.test_uri, headers=headers_with_user_agent)
@@ -166,8 +172,8 @@ class TransportTest(unittest.TestCase):
self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent']) self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent'])
def test_authorizedhttp_request_same_user_agent_already_in_headers(self): def test_authorizedhttp_request_same_user_agent_already_in_headers(self):
authorized_http = transport.AuthorizedHttp( authorized_http = transport.AuthorizedHttp(self.mock_credentials,
self.mock_credentials, http=self.mock_http) http=self.mock_http)
same_user_agent_header = {'user-agent': transport.GAM_USER_AGENT} same_user_agent_header = {'user-agent': transport.GAM_USER_AGENT}
authorized_http.request(self.test_uri, headers=same_user_agent_header) authorized_http.request(self.test_uri, headers=same_user_agent_header)
@@ -175,5 +181,5 @@ class TransportTest(unittest.TestCase):
self.assertIn('user-agent', final_headers) self.assertIn('user-agent', final_headers)
self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent']) self.assertIn(transport.GAM_USER_AGENT, final_headers['user-agent'])
# Make sure the header wasn't duplicated # Make sure the header wasn't duplicated
self.assertEqual( self.assertEqual(len(transport.GAM_USER_AGENT),
len(transport.GAM_USER_AGENT), len(final_headers['user-agent'])) len(final_headers['user-agent']))

View File

@@ -13,6 +13,7 @@ from gam import fileutils
from gam import transport from gam import transport
from gam.var import * from gam.var import *
class _DeHTMLParser(HTMLParser): class _DeHTMLParser(HTMLParser):
def __init__(self): def __init__(self):
@@ -23,14 +24,15 @@ class _DeHTMLParser(HTMLParser):
self.__text.append(data) self.__text.append(data)
def handle_charref(self, name): def handle_charref(self, name):
self.__text.append(chr(int(name[1:], 16)) if name.startswith('x') else chr(int(name))) self.__text.append(
chr(int(name[1:], 16)) if name.startswith('x') else chr(int(name)))
def handle_entityref(self, name): def handle_entityref(self, name):
cp = name2codepoint.get(name) cp = name2codepoint.get(name)
if cp: if cp:
self.__text.append(chr(cp)) self.__text.append(chr(cp))
else: else:
self.__text.append('&'+name) self.__text.append('&' + name)
def handle_starttag(self, tag, attrs): def handle_starttag(self, tag, attrs):
if tag == 'p': if tag == 'p':
@@ -53,7 +55,9 @@ class _DeHTMLParser(HTMLParser):
self.__text.append('\n\n') self.__text.append('\n\n')
def text(self): def text(self):
return re.sub(r'\n{2}\n+', '\n\n', re.sub(r'\n +', '\n', ''.join(self.__text))).strip() return re.sub(r'\n{2}\n+', '\n\n',
re.sub(r'\n +', '\n', ''.join(self.__text))).strip()
def dehtml(text): def dehtml(text):
try: try:
@@ -66,9 +70,11 @@ def dehtml(text):
print_exc(file=sys.stderr) print_exc(file=sys.stderr)
return text return text
def indentMultiLineText(message, n=0): def indentMultiLineText(message, n=0):
return message.replace('\n', '\n{0}'.format(' ' * n)).rstrip() return message.replace('\n', '\n{0}'.format(' ' * n)).rstrip()
def flatten_json(structure, key='', path='', flattened=None, listLimit=None): def flatten_json(structure, key='', path='', flattened=None, listLimit=None):
if flattened is None: if flattened is None:
flattened = {} flattened = {}
@@ -78,24 +84,38 @@ def flatten_json(structure, key='', path='', flattened=None, listLimit=None):
for i, item in enumerate(structure): for i, item in enumerate(structure):
if listLimit and (i >= listLimit): if listLimit and (i >= listLimit):
break break
flatten_json(item, f'{i}', '.'.join([item for item in [path, key] if item]), flattened=flattened, listLimit=listLimit) flatten_json(item,
f'{i}',
'.'.join([item for item in [path, key] if item]),
flattened=flattened,
listLimit=listLimit)
else: else:
for new_key, value in list(structure.items()): for new_key, value in list(structure.items()):
if new_key in ['kind', 'etag', '@type']: if new_key in ['kind', 'etag', '@type']:
continue continue
if value == NEVER_TIME: if value == NEVER_TIME:
value = 'Never' value = 'Never'
flatten_json(value, new_key, '.'.join([item for item in [path, key] if item]), flattened=flattened, listLimit=listLimit) flatten_json(value,
new_key,
'.'.join([item for item in [path, key] if item]),
flattened=flattened,
listLimit=listLimit)
return flattened return flattened
def formatTimestampYMD(timestamp): def formatTimestampYMD(timestamp):
return datetime.datetime.fromtimestamp(int(timestamp)/1000).strftime('%Y-%m-%d') return datetime.datetime.fromtimestamp(int(timestamp) /
1000).strftime('%Y-%m-%d')
def formatTimestampYMDHMS(timestamp): def formatTimestampYMDHMS(timestamp):
return datetime.datetime.fromtimestamp(int(timestamp)/1000).strftime('%Y-%m-%d %H:%M:%S') return datetime.datetime.fromtimestamp(int(timestamp) /
1000).strftime('%Y-%m-%d %H:%M:%S')
def formatTimestampYMDHMSF(timestamp): def formatTimestampYMDHMSF(timestamp):
return str(datetime.datetime.fromtimestamp(int(timestamp)/1000)) return str(datetime.datetime.fromtimestamp(int(timestamp) / 1000))
def formatFileSize(fileSize): def formatFileSize(fileSize):
if fileSize == 0: if fileSize == 0:
@@ -108,12 +128,14 @@ def formatFileSize(fileSize):
return f'{fileSize // ONE_MEGA_BYTES}mb' return f'{fileSize // ONE_MEGA_BYTES}mb'
return f'{fileSize // ONE_GIGA_BYTES}gb' return f'{fileSize // ONE_GIGA_BYTES}gb'
def formatMilliSeconds(millis): def formatMilliSeconds(millis):
seconds, millis = divmod(millis, 1000) seconds, millis = divmod(millis, 1000)
minutes, seconds = divmod(seconds, 60) minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
return f'{hours:02d}:{minutes:02d}:{seconds:02d}' return f'{hours:02d}:{minutes:02d}:{seconds:02d}'
def integerLimits(minVal, maxVal, item='integer'): def integerLimits(minVal, maxVal, item='integer'):
if (minVal is not None) and (maxVal is not None): if (minVal is not None) and (maxVal is not None):
return f'{item} {minVal}<=x<={maxVal}' return f'{item} {minVal}<=x<={maxVal}'
@@ -123,13 +145,18 @@ def integerLimits(minVal, maxVal, item='integer'):
return f'{item} x<={maxVal}' return f'{item} x<={maxVal}'
return f'{item} x' return f'{item} x'
def get_string(i, item, optional=False, minLen=1, maxLen=None): def get_string(i, item, optional=False, minLen=1, maxLen=None):
if i < len(sys.argv): if i < len(sys.argv):
argstr = sys.argv[i] argstr = sys.argv[i]
if argstr: if argstr:
if (len(argstr) >= minLen) and ((maxLen is None) or (len(argstr) <= maxLen)): if (len(argstr) >= minLen) and ((maxLen is None) or
(len(argstr) <= maxLen)):
return argstr return argstr
controlflow.system_error_exit(2, f'expected <{integerLimits(minLen, maxLen, "string length")} for {item}>') controlflow.system_error_exit(
2,
f'expected <{integerLimits(minLen, maxLen, "string length")} for {item}>'
)
if optional or (minLen == 0): if optional or (minLen == 0):
return '' return ''
controlflow.system_error_exit(2, f'expected a Non-empty <{item}>') controlflow.system_error_exit(2, f'expected a Non-empty <{item}>')
@@ -137,6 +164,7 @@ def get_string(i, item, optional=False, minLen=1, maxLen=None):
return '' return ''
controlflow.system_error_exit(2, f'expected a <{item}>') controlflow.system_error_exit(2, f'expected a <{item}>')
def get_delta(argstr, pattern): def get_delta(argstr, pattern):
tg = pattern.match(argstr.lower()) tg = pattern.match(argstr.lower())
if tg is None: if tg is None:
@@ -145,7 +173,7 @@ def get_delta(argstr, pattern):
delta = int(tg.group(2)) delta = int(tg.group(2))
unit = tg.group(3) unit = tg.group(3)
if unit == 'y': if unit == 'y':
deltaTime = datetime.timedelta(days=delta*365) deltaTime = datetime.timedelta(days=delta * 365)
elif unit == 'w': elif unit == 'w':
deltaTime = datetime.timedelta(weeks=delta) deltaTime = datetime.timedelta(weeks=delta)
elif unit == 'd': elif unit == 'd':
@@ -158,37 +186,45 @@ def get_delta(argstr, pattern):
return -deltaTime return -deltaTime
return deltaTime return deltaTime
def get_delta_date(argstr): def get_delta_date(argstr):
deltaDate = get_delta(argstr, DELTA_DATE_PATTERN) deltaDate = get_delta(argstr, DELTA_DATE_PATTERN)
if deltaDate is None: if deltaDate is None:
controlflow.system_error_exit(2, f'expected a <{DELTA_DATE_FORMAT_REQUIRED}>; got {argstr}') controlflow.system_error_exit(
2, f'expected a <{DELTA_DATE_FORMAT_REQUIRED}>; got {argstr}')
return deltaDate return deltaDate
def get_delta_time(argstr): def get_delta_time(argstr):
deltaTime = get_delta(argstr, DELTA_TIME_PATTERN) deltaTime = get_delta(argstr, DELTA_TIME_PATTERN)
if deltaTime is None: if deltaTime is None:
controlflow.system_error_exit(2, f'expected a <{DELTA_TIME_FORMAT_REQUIRED}>; got {argstr}') controlflow.system_error_exit(
2, f'expected a <{DELTA_TIME_FORMAT_REQUIRED}>; got {argstr}')
return deltaTime return deltaTime
def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False): def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False):
argstr = argstr.strip() argstr = argstr.strip()
if argstr: if argstr:
if argstr[0] in ['+', '-']: if argstr[0] in ['+', '-']:
today = datetime.date.today() today = datetime.date.today()
argstr = (datetime.datetime(today.year, today.month, today.day)+get_delta_date(argstr)).strftime(YYYYMMDD_FORMAT) argstr = (datetime.datetime(today.year, today.month, today.day) +
get_delta_date(argstr)).strftime(YYYYMMDD_FORMAT)
try: try:
dateTime = datetime.datetime.strptime(argstr, YYYYMMDD_FORMAT) dateTime = datetime.datetime.strptime(argstr, YYYYMMDD_FORMAT)
if returnTimeStamp: if returnTimeStamp:
return time.mktime(dateTime.timetuple())*1000 return time.mktime(dateTime.timetuple()) * 1000
if returnDateTime: if returnDateTime:
return dateTime return dateTime
return argstr return argstr
except ValueError: except ValueError:
controlflow.system_error_exit(2, f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>; got {argstr}') controlflow.system_error_exit(
2, f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>; got {argstr}')
elif minLen == 0: elif minLen == 0:
return '' return ''
controlflow.system_error_exit(2, f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>') controlflow.system_error_exit(2, f'expected a <{YYYYMMDD_FORMAT_REQUIRED}>')
def get_time_or_delta_from_now(time_string): def get_time_or_delta_from_now(time_string):
"""Get an ISO 8601 time or a positive/negative delta applied to now. """Get an ISO 8601 time or a positive/negative delta applied to now.
Args: Args:
@@ -200,8 +236,11 @@ def get_time_or_delta_from_now(time_string):
if time_string: if time_string:
if time_string[0] not in ['+', '-']: if time_string[0] not in ['+', '-']:
return time_string return time_string
return (datetime.datetime.utcnow() + get_delta_time(time_string)).isoformat() + 'Z' return (datetime.datetime.utcnow() +
controlflow.system_error_exit(2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>') get_delta_time(time_string)).isoformat() + 'Z'
controlflow.system_error_exit(
2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>')
def get_row_filter_date_or_delta_from_now(date_string): def get_row_filter_date_or_delta_from_now(date_string):
"""Get an ISO 8601 date or a positive/negative delta applied to now. """Get an ISO 8601 date or a positive/negative delta applied to now.
@@ -217,14 +256,19 @@ def get_row_filter_date_or_delta_from_now(date_string):
if deltaDate is None: if deltaDate is None:
return (False, DELTA_DATE_FORMAT_REQUIRED) return (False, DELTA_DATE_FORMAT_REQUIRED)
today = datetime.date.today() today = datetime.date.today()
return (True, (datetime.datetime(today.year, today.month, today.day)+deltaDate).isoformat()+'Z') return (True,
(datetime.datetime(today.year, today.month, today.day) +
deltaDate).isoformat() + 'Z')
try: try:
deltaDate = dateutil.parser.parse(date_string, ignoretz=True) deltaDate = dateutil.parser.parse(date_string, ignoretz=True)
return (True, datetime.datetime(deltaDate.year, deltaDate.month, deltaDate.day).isoformat()+'Z') return (True,
datetime.datetime(deltaDate.year, deltaDate.month,
deltaDate.day).isoformat() + 'Z')
except ValueError: except ValueError:
pass pass
return (False, YYYYMMDD_FORMAT_REQUIRED) return (False, YYYYMMDD_FORMAT_REQUIRED)
def get_row_filter_time_or_delta_from_now(time_string): def get_row_filter_time_or_delta_from_now(time_string):
"""Get an ISO 8601 time or a positive/negative delta applied to now. """Get an ISO 8601 time or a positive/negative delta applied to now.
Args: Args:
@@ -240,42 +284,48 @@ def get_row_filter_time_or_delta_from_now(time_string):
deltaTime = get_delta(time_string, DELTA_TIME_PATTERN) deltaTime = get_delta(time_string, DELTA_TIME_PATTERN)
if deltaTime is None: if deltaTime is None:
return (False, DELTA_TIME_FORMAT_REQUIRED) return (False, DELTA_TIME_FORMAT_REQUIRED)
return (True, (datetime.datetime.utcnow()+deltaTime).isoformat()+'Z') return (True,
(datetime.datetime.utcnow() + deltaTime).isoformat() + 'Z')
try: try:
deltaTime = dateutil.parser.parse(time_string, ignoretz=True) deltaTime = dateutil.parser.parse(time_string, ignoretz=True)
return (True, deltaTime.isoformat()+'Z') return (True, deltaTime.isoformat() + 'Z')
except ValueError: except ValueError:
pass pass
return (False, YYYYMMDDTHHMMSS_FORMAT_REQUIRED) return (False, YYYYMMDDTHHMMSS_FORMAT_REQUIRED)
def get_date_zero_time_or_full_time(time_string): def get_date_zero_time_or_full_time(time_string):
time_string = time_string.strip() time_string = time_string.strip()
if time_string: if time_string:
if YYYYMMDD_PATTERN.match(time_string): if YYYYMMDD_PATTERN.match(time_string):
return get_yyyymmdd(time_string)+'T00:00:00.000Z' return get_yyyymmdd(time_string) + 'T00:00:00.000Z'
return get_time_or_delta_from_now(time_string) return get_time_or_delta_from_now(time_string)
controlflow.system_error_exit(2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>') controlflow.system_error_exit(
2, f'expected a <{YYYYMMDDTHHMMSS_FORMAT_REQUIRED}>')
def md5_matches_file(local_file, expected_md5, exitOnError): def md5_matches_file(local_file, expected_md5, exitOnError):
f = fileutils.open_file(local_file, 'rb') f = fileutils.open_file(local_file, 'rb')
hash_md5 = md5() hash_md5 = md5()
for chunk in iter(lambda: f.read(4096), b""): for chunk in iter(lambda: f.read(4096), b''):
hash_md5.update(chunk) hash_md5.update(chunk)
actual_hash = hash_md5.hexdigest() actual_hash = hash_md5.hexdigest()
if exitOnError and actual_hash != expected_md5: if exitOnError and actual_hash != expected_md5:
controlflow.system_error_exit(6, f'actual hash was {actual_hash}. Exiting on corrupt file.') controlflow.system_error_exit(
6, f'actual hash was {actual_hash}. Exiting on corrupt file.')
return actual_hash == expected_md5 return actual_hash == expected_md5
URL_SHORTENER_ENDPOINT = 'https://gam-shortn.appspot.com/create' URL_SHORTENER_ENDPOINT = 'https://gam-shortn.appspot.com/create'
def shorten_url(long_url, httpc=None): def shorten_url(long_url, httpc=None):
if not httpc: if not httpc:
httpc = transport.create_http(timeout=10) httpc = transport.create_http(timeout=10)
headers = {'Content-Type': 'application/json', 'User-Agent': GAM_INFO} headers = {'Content-Type': 'application/json', 'User-Agent': GAM_INFO}
try: try:
payload = json.dumps({'long_url': long_url}) payload = json.dumps({'long_url': long_url})
resp, content = httpc.request( resp, content = httpc.request(URL_SHORTENER_ENDPOINT,
URL_SHORTENER_ENDPOINT,
'POST', 'POST',
payload, payload,
headers=headers) headers=headers)

File diff suppressed because it is too large Load Diff