def diff_app(client, app): print(shell.bold('Comparing "{}" on-disk definition to the current status in Marathon:'.format(app.id))) deployed = client.app_status(app.id)['app'] changes = diff_json(app.json, deployed) if not changes: print(shell.bold(shell.green('There were no differences.'))) return False else: print(shell.bold(shell.yellow('There were differences.'))) return True
def make_account_request(account, password): request = NewAccountRequest( user_name=account['user_name'], real_name=account['group_name'], is_group=True, calnet_uid=None, callink_oid=account['callink_oid'], email=account['email'], encrypted_password=encrypt_password( password, RSA.importKey(CREATE_PUBLIC_KEY), ), handle_warnings=NewAccountRequest.WARNINGS_WARN, ) print() print(bold('Pending account request:')) print( dedent("""\ User Name: {request.user_name} Group Name: {request.real_name} CalLink OID: {request.callink_oid} Email: {request.email} """).format(request=request)) return request
def make_account_request(account, password): request = NewAccountRequest( user_name=account['user_name'], real_name=account['group_name'], is_group=True, calnet_uid=None, callink_oid=account['callink_oid'], email=account['email'], encrypted_password=encrypt_password( password, RSA.importKey(CREATE_PUBLIC_KEY), ), handle_warnings=NewAccountRequest.WARNINGS_WARN, ) print() print(bold('Pending account request:')) print(dedent( """\ User Name: {request.user_name} Group Name: {request.real_name} CalLink OID: {request.callink_oid} Email: {request.email} """ ).format(request=request)) return request
def error_report(request, new_request, response): print(bold(red('Error: Entered unexpected state.'))) print(bold('An email has been sent to OCF staff')) error_report = dedent("""\ Error encountered running approve! The request we submitted was: {request} The request we submitted after being flagged (if any) was: {new_request} The response we received was: {response} """).format(request=request, new_request=new_request, reponse=response) send_problem_report(error_report)
def update_app(client, app): print(shell.bold(shell.yellow('Starting a new deployment.'))) deployed = client.app_status(app.id)['app'] # update the image tag (this doesn't quite match the on-disk tag) on_disk_tag = split_docker(app.json['container']['docker']['image']) deployed_tag = split_docker(deployed['container']['docker']['image']) new_json = copy.deepcopy(app.json) if on_disk_tag[0] == deployed_tag[0] and on_disk_tag[1] == 'latest': # The repo didn't change, and the local version is not pinned, # so keep it as whatever version is currently deployed. new_json['container']['docker']['image'] = '{}:{}'.format(*deployed_tag) else: # The repo or local version changed, so reset to whatever the local # one is. By default this is ${repo}:latest. new_json['container']['docker']['image'] = '{}:{}'.format(*on_disk_tag) client.deploy_app(app.id.lstrip('/'), new_json, report=unbuf_print) # make sure there are no longer any differences print(shell.bold('Confirming there are now no differences...')) new_deployed = client.app_status(app.id)['app'] if diff_json(app.json, new_deployed): print(shell.bold(shell.red( 'There were still differences in the on-disk and deployed version of "{}", ' 'even after making a new deployment.\n' '\n' 'This most likely means that the app config is missing some default value.\n' "This isn't a huge problem, but it *does* mean that every time this repo gets pushed, " 'we do a no-op push which wastes time :(\n' '\n' 'You should fix this, probably by taking the differences listed above and adding\n' "them to the app's config on disk.".format(app.id) ))) raise AssertionError('Deployment failed.') else: print(shell.bold(shell.green('OK!')))
def error_report(request, new_request, response): print(bold(red('Error: Entered unexpected state.'))) print(bold('An email has been sent to OCF staff')) error_report = dedent( """\ Error encountered running approve! The request we submitted was: {request} The request we submitted after being flagged (if any) was: {new_request} The response we received was: {response} """ ).format( request=request, new_request=new_request, reponse=response ) send_problem_report(error_report)
def run_periodic_functions() -> None: global delay_on_error # First, import urls so that views are imported, decorators are run, and # our periodic functions get registered. import ocfweb.urls # noqa was_error = False for pf in periodic_functions: if pf.seconds_since_last_update() >= pf.period: _logger.info(bold(green(f'Updating periodic function: {pf}'))) try: pf.update() except Exception as ex: was_error = True if isinstance(ex, KeyboardInterrupt) or settings.DEBUG: raise try: send_problem_report( dedent("""\ An exception occurred in an ocfweb periodic function: {traceback} Periodic function: * Key: {pf.function_call_key} * Last Update: {last_update} ({seconds_since_last_update} seconds ago) * Period: {pf.period} * TTL: {pf.ttl} The background process will now pause for {delay} seconds. """).format( traceback=format_exc(), pf=pf, last_update=pf.last_update(), seconds_since_last_update=pf. seconds_since_last_update(), delay=delay_on_error, ), ) _logger.error(format_exc()) except Exception as ex: print(ex) # just in case it errors again here send_problem_report( dedent("""\ An exception occured in ocfweb, but we errored trying to report it: {traceback} """).format(traceback=format_exc()), ) raise else: _logger.debug(bold( yellow(f'Not updating periodic function: {pf}'))) if was_error: delay_on_error = min(DELAY_ON_ERROR_MAX, delay_on_error * 2) time.sleep(delay_on_error) else: delay_on_error = max(DELAY_ON_ERROR_MIN, delay_on_error / 2)
def main(): def pause_error_msg(): input('Press enter to edit group information (or Ctrl-C to exit)...') parser = ArgumentParser(description='Create new OCF group accounts.') parser.add_argument('oid', type=int, nargs='?', help='CalLink OID for the group.') args = parser.parse_args() group_name, callink_oid, email = get_group_information(args.oid) content = TEMPLATE.format(group_name=group_name, callink_oid=callink_oid, email=email) while True: content = edit_file(content) try: account = yaml.safe_load(content) except yaml.YAMLError as ex: print('Error parsing your YAML:') print(ex) pause_error_msg() continue missing_key = False for key in ['user_name', 'group_name', 'callink_oid', 'email']: if account.get(key) is None: print('Missing value for key: ' + key) missing_key = True if missing_key: pause_error_msg() continue try: password = prompt_for_new_password( validator=lambda pwd: validate_password( account['user_name'], pwd), ) except KeyboardInterrupt: # we want to allow cancelling during the "enter password" stage # without completely exiting approve print() pause_error_msg() continue request = make_account_request(account, password) if input('Submit request? [yN] ') not in ('y', 'Y'): pause_error_msg() continue tasks, celery, response = create_account(request) new_request = None if response.status == NewAccountResponse.REJECTED: print( bold( red('Account requested was rejected for the following reasons:' ))) for error in response.errors: print(red(' - {}'.format(error))) pause_error_msg() continue elif response.status == NewAccountResponse.FLAGGED: print( bold( yellow( 'Account requested was flagged for the following reasons:' ))) for error in response.errors: print(yellow(' - {}'.format(error))) print( bold( 'You can either create the account anyway, or go back and ' 'modify the request.')) choice = input('Create the account anyway? [yN] ') if choice in ('y', 'Y'): new_request = request._replace( handle_warnings=NewAccountRequest.WARNINGS_CREATE, ) task = tasks.validate_then_create_account.delay(new_request) response = wait_for_task(celery, task) else: pause_error_msg() continue if response.status == NewAccountResponse.CREATED: print(bold(green('Account created!'))) print('Your account was created successfully.') print('You\'ve been sent an email with more information.') return else: # this shouldn't be possible; we must have entered some weird state error_report(request, new_request, response) pause_error_msg()
def run_periodic_functions(): global delay_on_error # First, import urls so that views are imported, decorators are run, and # our periodic functions get registered. import ocfweb.urls # noqa was_error = False for pf in periodic_functions: if pf.seconds_since_last_update() >= pf.period: _logger.info(bold(green('Updating periodic function: {}'.format(pf)))) try: pf.update() except Exception as ex: was_error = True if isinstance(ex, KeyboardInterrupt) or settings.DEBUG: raise try: send_problem_report(dedent( """\ An exception occurred in an ocfweb periodic function: {traceback} Periodic function: * Key: {pf.function_call_key} * Last Update: {last_update} ({seconds_since_last_update} seconds ago) * Period: {pf.period} * TTL: {pf.ttl} The background process will now pause for {delay} seconds. """ ).format( traceback=format_exc(), pf=pf, last_update=pf.last_update(), seconds_since_last_update=pf.seconds_since_last_update(), delay=delay_on_error, )) _logger.error(format_exc()) except Exception as ex: print(ex) # just in case it errors again here send_problem_report(dedent( """\ An exception occured in ocfweb, but we errored trying to report it: {traceback} """ ).format(traceback=format_exc())) raise else: _logger.debug(bold(yellow('Not updating periodic function: {}'.format(pf)))) if was_error: delay_on_error = min(DELAY_ON_ERROR_MAX, delay_on_error * 2) time.sleep(delay_on_error) else: delay_on_error = max(DELAY_ON_ERROR_MIN, delay_on_error / 2)
def main(): def_group_name = '' def_callink_oid = '' def_email = '' parser = ArgumentParser(description='Create new OCF group accounts.') parser.add_argument('oid', type=int, nargs='?', help='CalLink OID for the group.') args = parser.parse_args() if args.oid: group = group_by_oid(args.oid) if not group: print(red('No group with OID {}').format(args.oid)) return if group['accounts']: print(yellow( 'Warning: there is an existing group account with OID {}: {}'.format( args.oid, ', '.join(group['accounts']), ), )) input('Press any key to continue...') def_group_name = group['name'] def_callink_oid = args.oid def_email = group['email'] content = TEMPLATE.format( group_name=def_group_name, callink_oid=def_callink_oid, email=def_email ) while True: content = edit_file(content) try: account = yaml.safe_load(content) except yaml.YAMLError as ex: print('Error parsing your YAML:') print(ex) input('Press enter to continue...') continue missing_key = False for key in ['user_name', 'group_name', 'callink_oid', 'email']: if account.get(key) is None: print('Missing value for key: ' + key) missing_key = True if missing_key: input('Press enter to continue...') continue try: password = prompt_for_new_password( validator=lambda pwd: validate_password( account['user_name'], pwd), ) except KeyboardInterrupt: # we want to allow cancelling during the "enter password" stage # without completely exiting approve print() input('Press enter to start over (or ^C again to cancel)...') continue request = NewAccountRequest( user_name=account['user_name'], real_name=account['group_name'], is_group=True, calnet_uid=None, callink_oid=account['callink_oid'], email=account['email'], encrypted_password=encrypt_password( password, RSA.importKey(CREATE_PUBLIC_KEY), ), handle_warnings=NewAccountRequest.WARNINGS_WARN, ) print() print(bold('Pending account request:')) print(dedent( """\ User Name: {request.user_name} Group Name: {request.real_name} CalLink OID: {request.callink_oid} Email: {request.email} """ ).format(request=request)) if input('Submit request? [yN] ') != 'y': input('Press enter to continue.') continue conf = ConfigParser() conf.read('/etc/ocf-create/ocf-create.conf') celery = Celery( broker=conf.get('celery', 'broker'), backend=conf.get('celery', 'backend'), ) tasks = get_tasks(celery) task = tasks.validate_then_create_account.delay(request) response = wait_for_task(celery, task) new_request = None if response.status == NewAccountResponse.REJECTED: print(bold(red( 'Account requested was rejected for the following reasons:' ))) for error in response.errors: print(red(' - {}'.format(error))) input('Press enter to start over (or ^C to cancel)...') continue elif response.status == NewAccountResponse.FLAGGED: print(bold(yellow( 'Account requested was flagged for the following reasons:' ))) for error in response.errors: print(yellow(' - {}'.format(error))) print(bold( 'You can either create the account anyway, or go back and ' 'modify the request.' )) choice = input('Create the account anyway? [yN] ') if choice in ('y', 'Y'): new_request = request._replace( handle_warnings=NewAccountRequest.WARNINGS_CREATE, ) task = tasks.validate_then_create_account.delay(new_request) response = wait_for_task(celery, task) else: input('Starting over, press enter to continue...') continue if response.status == NewAccountResponse.CREATED: print(bold(green('Account created!'))) print('Your account was created successfully.') print('You\'ve been sent an email with more information.') return else: # this shouldn't be possible; we must have entered some weird state # TODO: report via ocflib print(bold(red('Error: Entered unexpected state.'))) print(red('The request we submitted was:')) print(red(request)) print(red('The new request we submitted (if any) was:')) print(red(new_request)) print(red('The response we received was:')) print(red(response)) print(bold(red('Not really sure what to do here, sorry.'))) input('Press enter to start over...')
def main(): def pause_error_msg(): input('Press enter to edit group information (or Ctrl-C to exit)...') parser = ArgumentParser(description='Create new OCF group accounts.') parser.add_argument('oid', type=int, nargs='?', help='CalLink OID for the group.') args = parser.parse_args() group_name, callink_oid, email = get_group_information(args.oid) content = TEMPLATE.format( group_name=group_name, callink_oid=callink_oid, email=email ) while True: content = edit_file(content) try: account = yaml.safe_load(content) except yaml.YAMLError as ex: print('Error parsing your YAML:') print(ex) pause_error_msg() continue missing_key = False for key in ['user_name', 'group_name', 'callink_oid', 'email']: if account.get(key) is None: print('Missing value for key: ' + key) missing_key = True if missing_key: pause_error_msg() continue try: password = prompt_for_new_password( validator=lambda pwd: validate_password( account['user_name'], pwd), ) except KeyboardInterrupt: # we want to allow cancelling during the "enter password" stage # without completely exiting approve print() pause_error_msg() continue request = make_account_request(account, password) if input('Submit request? [yN] ') not in ('y', 'Y'): pause_error_msg() continue tasks, celery, response = create_account(request) new_request = None if response.status == NewAccountResponse.REJECTED: print(bold(red( 'Account requested was rejected for the following reasons:' ))) for error in response.errors: print(red(' - {}'.format(error))) pause_error_msg() continue elif response.status == NewAccountResponse.FLAGGED: print(bold(yellow( 'Account requested was flagged for the following reasons:' ))) for error in response.errors: print(yellow(' - {}'.format(error))) print(bold( 'You can either create the account anyway, or go back and ' 'modify the request.' )) choice = input('Create the account anyway? [yN] ') if choice in ('y', 'Y'): new_request = request._replace( handle_warnings=NewAccountRequest.WARNINGS_CREATE, ) task = tasks.validate_then_create_account.delay(new_request) response = wait_for_task(celery, task) else: pause_error_msg() continue if response.status == NewAccountResponse.CREATED: print(bold(green('Account created!'))) print('Your account was created successfully.') print('You\'ve been sent an email with more information.') return else: # this shouldn't be possible; we must have entered some weird state error_report(request, new_request, response) pause_error_msg()