def set_config_file(config_file=DEFAULT_CONFIG_FILE): while True: url = click.prompt( 'Please enter the SLR base URL (e.g. https://slo-metrics.example.com)' ) with Action('Checking {}..'.format(url)): requests.get(url, timeout=5, allow_redirects=False) zmon_url = click.prompt( 'Please enter the ZMON URL (e.g. https://demo.zmon.io)') with Action('Checking {}..'.format(zmon_url)): requests.get(zmon_url, timeout=5, allow_redirects=False) break data = { 'url': url, 'zmon_url': zmon_url, } fn = os.path.expanduser(config_file) with Action('Writing configuration to {}..'.format(fn)): with open(fn, 'w') as fd: json.dump(data, fd) return data
def do_respawn_auto_scaling_group(asg_name: str, group: dict, region: str, instances_to_terminate: set, inplace: bool): """ Respawn ASG. """ asg = BotoClientProxy("autoscaling", region) with Action("Suspending scaling processes for {}..".format(asg_name)): asg.suspend_processes(AutoScalingGroupName=asg_name, ScalingProcesses=SCALING_PROCESSES_TO_SUSPEND) extra_capacity = 0 if inplace else 1 new_min_size = group["MinSize"] + extra_capacity new_max_size = group["MaxSize"] + extra_capacity new_desired_capacity = group["DesiredCapacity"] + extra_capacity # TODO: error handling (rollback in case of exception?) while instances_to_terminate: current_group = scale_out(asg, asg_name, region, new_min_size, new_max_size, new_desired_capacity) instance = sorted(instances_to_terminate)[0] terminate_instance(asg, region, current_group, instance) instances_to_terminate.remove(instance) with Action("Resetting Auto Scaling Group to original capacity " "({MinSize}-{DesiredCapacity}-{MaxSize})..".format_map(group)): asg.update_auto_scaling_group( AutoScalingGroupName=asg_name, MinSize=group["MinSize"], MaxSize=group["MaxSize"], DesiredCapacity=group["DesiredCapacity"], ) with Action("Resuming scaling processes for {}..".format(asg_name)): asg.resume_processes(AutoScalingGroupName=asg_name)
def configure(config): '''Configure GitHub access''' emails = config.get('emails', []) if not emails: try: emails = [get_git_email()] except: pass emails = click.prompt('Your email addresses (comma separated)', default=','.join(emails) or None) token = click.prompt('Your personal GitHub access token', hide_input=True, default=config.get('github_access_token')) emails = list([mail.strip() for mail in emails.split(',')]) config = {'emails': emails, 'github_access_token': token} repositories = {} with Action('Scanning repositories..') as act: for repo in get_repos(token): repositories[repo['url']] = repo act.progress() path = os.path.join(CONFIG_DIR, 'repositories.yaml') os.makedirs(CONFIG_DIR, exist_ok=True) with open(path, 'w') as fd: yaml.safe_dump(repositories, fd) with Action('Storing configuration..'): stups_cli.config.store_config(config, 'github-maintainer-cli')
def do_respawn_auto_scaling_group(asg_name: str, group: dict, region: str, instances_to_terminate: set, instances_ok: set, inplace: bool): asg = boto3.client('autoscaling', region) with Action('Suspending scaling processes for {}..'.format(asg_name)): asg.suspend_processes(AutoScalingGroupName=asg_name, ScalingProcesses=SCALING_PROCESSES_TO_SUSPEND) extra_capacity = 0 if inplace else 1 new_min_size = group['MinSize'] + extra_capacity new_max_size = group['MaxSize'] + extra_capacity new_desired_capacity = group['DesiredCapacity'] + extra_capacity # TODO: error handling (rollback in case of exception?) while instances_to_terminate: current_group = scale_out(asg, asg_name, region, new_min_size, new_max_size, new_desired_capacity) instance = sorted(instances_to_terminate)[0] terminate_instance(asg, region, current_group, instance) instances_to_terminate.remove(instance) with Action( 'Resetting Auto Scaling Group to original capacity ({}-{}-{})..'. format(group['MinSize'], group['DesiredCapacity'], group['MaxSize'])): asg.update_auto_scaling_group(AutoScalingGroupName=asg_name, MinSize=group['MinSize'], MaxSize=group['MaxSize'], DesiredCapacity=group['DesiredCapacity']) with Action('Resuming scaling processes for {}..'.format(asg_name)): asg.resume_processes(AutoScalingGroupName=asg_name)
def check_iam_role(application_id: str, bucket_name: str, region: str): role_name = 'app-{}'.format(application_id) with Action('Checking IAM role {}..'.format(role_name)): iam = boto3.client('iam') exists = False try: iam.get_role(RoleName=role_name) exists = True except: pass assume_role_policy_document = {'Statement': [{'Action': 'sts:AssumeRole', 'Effect': 'Allow', 'Principal': {'Service': 'ec2.amazonaws.com'}, 'Sid': ''}], 'Version': '2008-10-17'} if not exists: with Action('Creating IAM role {}..'.format(role_name)): iam.create_role(RoleName=role_name, AssumeRolePolicyDocument=json.dumps(assume_role_policy_document)) update_policy = bucket_name is not None and (not exists or click.confirm('IAM role {} already exists. '.format(role_name) + 'Do you want Senza to overwrite the role policy?')) if update_policy: with Action('Updating IAM role policy of {}..'.format(role_name)): policy = get_iam_role_policy(application_id, bucket_name, region) iam.put_role_policy(RoleName=role_name, PolicyName=role_name, PolicyDocument=json.dumps(policy))
def traffic(stack_name: str, stack_version: Optional[str], percentage: Optional[int], region: Optional[str], remote: Optional[str], output: Optional[str]): '''Manage stack traffic''' lizzy = setup_lizzy_client(remote) if percentage is None: stack_reference = [stack_name] with Action('Requesting traffic info..'): stack_weights = [] for stack in lizzy.get_stacks(stack_reference, region=region): if stack['status'] in ['CREATE_COMPLETE', 'UPDATE_COMPLETE']: stack_id = '{stack_name}-{version}'.format_map(stack) traffic = lizzy.get_traffic(stack_id, region=region) stack_weights.append({ 'stack_name': stack_name, 'version': stack['version'], 'identifier': stack_id, 'weight%': traffic['weight'] }) cols = 'stack_name version identifier weight%'.split() with OutputFormat(output): print_table(cols, sorted(stack_weights, key=lambda x: x['identifier'])) else: with Action('Requesting traffic change..'): stack_id = '{stack_name}-{stack_version}'.format_map(locals()) lizzy.traffic(stack_id, percentage, region=region)
def check_s3_bucket(bucket_name: str, region: str): with Action("Checking S3 bucket {}..".format(bucket_name)): exists = False try: s3 = boto.s3.connect_to_region(region) exists = s3.lookup(bucket_name, validate=True) except: pass if not exists: with Action("Creating S3 bucket {}...".format(bucket_name)): s3.create_bucket(bucket_name, location=region)
def check_iam_role(application_id: str, bucket_name: str, region: str): role_name = "app-{}".format(application_id) with Action("Checking IAM role {}..".format(role_name)): iam = BotoClientProxy("iam") try: iam.get_role(RoleName=role_name) exists = True except botocore.exceptions.ClientError: exists = False assume_role_policy_document = { "Statement": [{ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Sid": "", }], "Version": "2008-10-17", } create = False if not exists: create = confirm( "IAM role {} does not exist. " "Do you want Senza to create it now?".format(role_name), default=True, ) if create: with Action("Creating IAM role {}..".format(role_name)): iam.create_role( RoleName=role_name, AssumeRolePolicyDocument=json.dumps( assume_role_policy_document), ) attach_mint_read_policy = bucket_name is not None and ( (not exists and create) or (exists and confirm("IAM role {} already exists. ".format(role_name) + "Do you want Senza to overwrite the role policy?"))) if attach_mint_read_policy: with Action("Updating IAM role policy of {}..".format(role_name)): mint_read_policy = create_mint_read_policy_document( application_id, bucket_name, region) iam.put_role_policy( RoleName=role_name, PolicyName=role_name, PolicyDocument=json.dumps(mint_read_policy), ) attach_cross_stack_policy(exists, create, role_name, iam)
def check_s3_bucket(bucket_name: str, region: str): s3 = boto3.resource('s3', region) with Action("Checking S3 bucket {}..".format(bucket_name)): exists = False try: s3.meta.client.head_bucket(Bucket=bucket_name) exists = True except: pass if not exists: with Action("Creating S3 bucket {}...".format(bucket_name)): s3.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': region})
def create(definition, region, version, parameter, disable_rollback, dry_run, force): '''Create a new Cloud Formation stack from the given Senza definition file''' input = definition region = get_region(region) check_credentials(region) args = parse_args(input, region, version, parameter) with Action('Generating Cloud Formation template..'): data = evaluate(input.copy(), args, force) cfjson = json.dumps(data, sort_keys=True, indent=4) stack_name = "{0}-{1}".format(input["SenzaInfo"]["StackName"], version) if len(stack_name) > 128: raise click.UsageError('Stack name "{}" cannot exceed 128 characters. '.format(stack_name) + ' Please choose another name/version.') parameters = [] for name, parameter in data.get("Parameters", {}).items(): parameters.append([name, getattr(args, name, None)]) tags = { "Name": stack_name, "StackName": input["SenzaInfo"]["StackName"], "StackVersion": version } if "OperatorTopicId" in input["SenzaInfo"]: topic = input["SenzaInfo"]["OperatorTopicId"] topic_arn = resolve_topic_arn(region, topic) if not topic_arn: raise click.UsageError('SNS topic "{}" does not exist'.format(topic)) topics = [topic_arn] else: topics = None capabilities = get_required_capabilities(data) cf = boto.cloudformation.connect_to_region(region) with Action('Creating Cloud Formation stack {}..'.format(stack_name)): try: if dry_run: info('**DRY-RUN** {}'.format(topics)) else: cf.create_stack(stack_name, template_body=cfjson, parameters=parameters, tags=tags, notification_arns=topics, disable_rollback=disable_rollback, capabilities=capabilities) except boto.exception.BotoServerError as e: if e.error_code == 'AlreadyExistsException': raise click.UsageError('Stack {} already exists. Please choose another version.'.format(stack_name)) else: raise
def main(): # parser = argparse.ArgumentParser() # parser.add_argument('from') # parser.add_argument('to') # parser.add_argument('file') # args = parser.parse_args() hosts_file = Path('/etc/hosts') with hosts_file.open() as fd: old_contents = fd.read() backup_file = hosts_file.with_suffix('.local-cname-backup') with backup_file.open('w') as fd: fd.write(old_contents) try: while True: entries = [] cname_file = Path('/etc/cnames') with cname_file.open() as fd: for line in fd: (cnameFrom, cnameTo) = line.strip().split('=') print('resoving:' + cnameTo) with Action('Resolving {} ..'.format(cnameTo)): results = socket.getaddrinfo(cnameTo, 80, type=socket.SOCK_STREAM) for result in results: family, type, proto, canonname, sockaddr = result if family in (socket.AF_INET, socket.AF_INET6): ip = sockaddr[0] entries.append((cnameFrom, ip)) info('Current entries:') for hostname, ip in entries: info('{} -> {}'.format(hostname, ip)) with Action('Writing {} ..'.format(hosts_file)): with hosts_file.open('w') as fd: fd.write(old_contents) fd.write( '#### Start of entries generated by local-cnames\n') for hostname, ip in entries: fd.write('{} {}\n'.format(ip, hostname)) time.sleep(60) except KeyboardInterrupt: # ignore, do not print stacktrace pass finally: backup_file.rename(hosts_file)
def test_action(): try: with Action('Try and fail..'): raise Exception() except: pass with Action('Perform and progress..') as act: act.progress() act.error('failing..') with Action('Perform and progress..') as act: act.progress() act.warning('warning..') with Action('Perform and progress..') as act: act.progress() act.ok('all fine') with Action('Perform and progress..') as act: act.progress() with Action('Perform, progress and done', ok_msg='DONE') as act: act.progress() with Action('Perform action new line', nl=True): print('In new line!') with pytest.raises(SystemExit): with Action('Try and fail badly..') as act: act.fatal_error('failing..')
def edit_etc_hosts(hosts_file, backup_file, args): with hosts_file.open() as fd: old_contents = fd.read() HEADER = '#### Start of entries generated by local-cname' if HEADER in old_contents: error('{} seems to have already been modified by local-cname.'.format( hosts_file)) info('Remove the local-cname header line from this file to proceed.') sys.exit(1) with backup_file.open('w') as fd: fd.write(old_contents) try: while True: entries = [] with Action('Resolving {} ..'.format(args.to)): results = socket.getaddrinfo(args.to, 80, type=socket.SOCK_STREAM) for result in results: family, type, proto, canonname, sockaddr = result if family in (socket.AF_INET, socket.AF_INET6): ip = sockaddr[0] entries.append((getattr(args, 'from'), ip)) info('Current entries:') for hostname, ip in entries: info('{} -> {}'.format(hostname, ip)) with Action('Writing {} ..'.format(hosts_file)): with hosts_file.open('w') as fd: fd.write(old_contents) fd.write('{}\n'.format(HEADER)) for hostname, ip in entries: fd.write('{} {}\n'.format(ip, hostname)) time.sleep(60) except KeyboardInterrupt: # ignore, do not print stacktrace pass finally: try: backup_file.rename(hosts_file) except OSError: with hosts_file.open('w') as fd: fd.write(old_contents) os.remove(backup_file)
def find_taupage_amis(regions: list) -> dict: ''' Find latest Taupage AMI for each region ''' result = {} for region in regions: with Action('Finding latest Taupage AMI in {}..'.format(region)): ec2 = boto3.resource('ec2', region) filters = [{ 'Name': 'name', 'Values': ['*Taupage-AMI-*'] }, { 'Name': 'is-public', 'Values': ['false'] }, { 'Name': 'state', 'Values': ['available'] }, { 'Name': 'root-device-type', 'Values': ['ebs'] }] images = list(ec2.images.filter(Filters=filters)) if not images: raise Exception('No Taupage AMI found') most_recent_image = sorted(images, key=lambda i: i.name)[-1] result[region] = most_recent_image return result
def launch_instance(region: str, ip: dict, ami: object, subnet: dict, security_group_id: str, is_seed: bool, options: dict): node_type = 'SEED' if is_seed else 'NORMAL' msg = 'Launching {} node {} in {}..'.format(node_type, ip['_defaultIp'], region) with Action(msg) as act: ec2 = boto3.client('ec2', region_name=region) mappings = ami.block_device_mappings block_devices = override_ephemeral_block_devices(mappings) volume_name = '{}-{}'.format(options['cluster_name'], ip['PrivateIp']) create_tagged_volume(ec2, options, subnet['AvailabilityZone'], volume_name) user_data = options['user_data'] user_data['volumes']['ebs']['/dev/xvdf'] = volume_name taupage_user_data = dump_user_data_for_taupage(user_data) resp = ec2.run_instances( ImageId=ami.id, MinCount=1, MaxCount=1, SecurityGroupIds=[security_group_id], UserData=taupage_user_data, InstanceType=options['instance_type'], SubnetId=subnet['SubnetId'], PrivateIpAddress=ip['PrivateIp'], BlockDeviceMappings=block_devices, IamInstanceProfile={'Arn': options['instance_profile']['Arn']}, DisableApiTermination=not (options['no_termination_protection'])) instance = resp['Instances'][0] instance_id = instance['InstanceId'] ec2.create_tags(Resources=[instance_id], Tags=[{ 'Key': 'Name', 'Value': options['cluster_name'] }]) # wait for instance to initialize before we can assign a # public IP address to it or tag the attached volume while True: resp = ec2.describe_instances(InstanceIds=[instance_id]) instance = resp['Reservations'][0]['Instances'][0] if instance['State']['Name'] != 'pending': break time.sleep(5) act.progress() if options['use_dmz']: ec2.associate_address(InstanceId=instance_id, AllocationId=ip['AllocationId']) alarm_sns_topic_arn = None if options['alarm_topics']: alarm_sns_topic_arn = options['alarm_topics'][region] create_auto_recovery_alarm(region, options['cluster_name'], instance_id, alarm_sns_topic_arn)
def sli_update(obj, product_name, name, sli_file): """Update SLI for a product""" client = get_client(obj) product = client.product_list(name=product_name) if not product: fatal_error('Product {} does not exist'.format(product_name)) product = product[0] slis = client.sli_list(product, name) if not slis: fatal_error('SLI {} does not exist'.format(name)) with Action('Updating SLI {} for product: {}'.format(name, product_name), nl=True) as act: sli = json.load(sli_file) validate_sli(obj, sli, act) if not act.errors: sli['uri'] = slis[0]['uri'] s = client.sli_update(sli) print(json.dumps(s, indent=4))
def ensure_kubectl(): kubectl = os.path.join(click.get_app_dir(APP_NAME), 'kubectl') if not os.path.exists(kubectl): os.makedirs(os.path.dirname(kubectl), exist_ok=True) platform = sys.platform # linux or darwin arch = 'amd64' # FIXME: hardcoded value url = KUBECTL_URL_TEMPLATE.format(version=KUBECTL_VERSION, os=platform, arch=arch) with Action('Downloading {}..'.format(url)) as act: response = requests.get(url, stream=True) local_file = kubectl + '.download' with open(local_file, 'wb') as fd: for i, chunk in enumerate( response.iter_content(chunk_size=4096)): if chunk: # filter out keep-alive new chunks fd.write(chunk) if i % 256 == 0: # every 1MB act.progress() os.chmod(local_file, 0o755) os.rename(local_file, kubectl) return kubectl
def change_version_traffic(stack_ref: StackReference, percentage: float, region: str): versions = list(get_stack_versions(stack_ref.name, region)) arns = [] for each_version in versions: arns = arns + each_version.notification_arns identifier_versions = collections.OrderedDict( (version.identifier, version.version) for version in versions) version = get_version(versions, stack_ref.version) identifier = version.identifier if not version.domain: raise click.UsageError('Stack {} version {} has ' 'no domain'.format(version.name, version.version)) percentage = int(percentage * PERCENT_RESOLUTION) known_record_weights, partial_count, partial_sum = get_weights(version.dns_name, identifier, identifier_versions.keys()) if partial_count == 0 and percentage == 0: # disable the last remaining version new_record_weights = {i: 0 for i in known_record_weights.keys()} message = ('DNS record "{dns_name}" will be removed from that ' 'stack'.format(dns_name=version.dns_name)) ok(msg=message) else: with Action('Calculating new weights..'): compensations = {} if partial_count: delta = int((FULL_PERCENTAGE - percentage - partial_sum) / partial_count) else: delta = 0 if percentage > 0: # will put the only last version to full traffic percentage compensations[identifier] = FULL_PERCENTAGE - percentage percentage = int(FULL_PERCENTAGE) new_record_weights, deltas = calculate_new_weights(delta, identifier, known_record_weights, percentage) total_weight = sum(new_record_weights.values()) calculation_error = FULL_PERCENTAGE - total_weight if calculation_error and calculation_error < FULL_PERCENTAGE: compensate(calculation_error, compensations, identifier, new_record_weights, partial_count, percentage, identifier_versions) assert sum(new_record_weights.values()) == FULL_PERCENTAGE message = dump_traffic_changes(stack_ref.name, identifier, identifier_versions, known_record_weights, new_record_weights, compensations, deltas) print_traffic_changes(message) inform_sns(arns, message, region) set_new_weights(version.dns_name, identifier, version.lb_dns_name, new_record_weights, region)
def init(ctx, configuration_file): '''Initialize a new AWS account with Sevenseconds''' config = yaml.safe_load(configuration_file) region = config.get('region') check_credentials(region) module = importlib.import_module( 'hoops.templates.sevenseconds.configuration') variables = {} # variables = module.gather_user_variables(variables, region) with Action('Generating Sevenseconds configuration...'): definition_file = tempfile.NamedTemporaryFile() try: definition = module.generate_definition(variables) definition_file.write(definition.encode('utf-8')) definition_file.seek(0, 0) finally: ctx.invoke(sevenseconds.cli.configure, file=definition_file, account_name_pattern='*', saml_user=None, saml_password=None, dry_run=True) definition_file.close()
def product_update(obj, name, new_name, new_email, product_group_name): """Update product""" client = get_client(obj) ps = client.product_list(name) if not ps: fatal_error('Product {} does not exist'.format(name)) with Action('Updating product: {}'.format(name), nl=True): p = ps[0] if new_name: p['name'] = new_name if new_email: p['email'] = new_email if product_group_name: pgs = client.product_group_list(name=product_group_name) if not pgs: fatal_error('Product group {} does not exist'.format( product_group_name)) p['product_group_uri'] = pgs[0]['uri'] p = client.product_update(p) print(json.dumps(p, indent=4))
def slo_create(obj, product_name, title, description, slo_file): """ Create SLO. If SLO file is used, then --title and --description will be ignored. """ client = get_client(obj) product = client.product_list(name=product_name) if not product: fatal_error('Product {} does not exist'.format(product_name)) product = product[0] with Action('Creating SLO for product: {}'.format(product_name), nl=True) as act: if slo_file: slo = json.load(slo_file) else: slo = {'title': title, 'description': description} validate_slo(slo, act) if not act.errors: new_slo = client.slo_create(product, slo['title'], slo.get('description', '')) print(json.dumps(new_slo, indent=4)) for target in slo.get('targets', []): t = client.target_create(new_slo, target['sli_uri'], target_from=target['from'], target_to=target['to']) act.ok('Created a new target') print(json.dumps(t, indent=4))
def push_entity(obj, entity): """Push one or more entities""" client = get_client(obj.config) if (entity.endswith('.json') or entity.endswith('.yaml')) and os.path.exists(entity): with open(entity, 'rb') as fd: data = yaml.safe_load(fd) else: data = json.loads(entity) if not isinstance(data, list): data = [data] with Action('Creating new entities ...', nl=True) as act: for e in data: action('Creating entity {} ...'.format(e['id'])) try: client.add_entity(e) ok() except ZmonArgumentError as e: act.error(str(e)) except requests.HTTPError as e: log_http_exception(e, act) except Exception as e: act.error('Failed: {}'.format(str(e)))
def delete(stack_ref: List[str], region: str, dry_run: bool, force: bool, remote: str): """Delete Cloud Formation stacks""" lizzy = setup_lizzy_client(remote) stack_refs = get_stack_refs(stack_ref) all_with_version = all(stack.version is not None for stack in stack_refs) # this is misleading but it's the current behaviour of senza # TODO Lizzy list (stack_refs) to see if it actually matches more than one stack # to match senza behaviour if (not all_with_version and not dry_run and not force): fatal_error( 'Error: {} matching stacks found. '.format(len(stack_refs)) + 'Please use the "--force" flag if you really want to delete multiple stacks.') # TODO pass force option to agent output = '' for stack in stack_refs: if stack.version is not None: stack_id = '{stack.name}-{stack.version}'.format(stack=stack) else: stack_id = stack.name with Action("Requesting stack '{stack_id}' deletion..", stack_id=stack_id): output = lizzy.delete(stack_id, region=region, dry_run=dry_run) print(output)
def init(definition_file, region, template, user_variable): '''Initialize a new Senza definition''' region = get_region(region) check_credentials(region) account_info = AccountArguments(region=region) templates = [] for mod in os.listdir(os.path.join(os.path.dirname(__file__), 'templates')): if not mod.startswith('_'): templates.append(mod.split('.')[0]) while template not in templates: template = choice('Please select the project template', [(t, get_template_description(t)) for t in sorted(templates)], default='webapp') module = importlib.import_module('senza.templates.{}'.format(template)) variables = {} for key_val in user_variable: key, val = key_val variables[key] = val variables = module.gather_user_variables(variables, region, account_info) with Action('Generating Senza definition file {}..'.format( definition_file.name)): definition = module.generate_definition(variables) definition_file.write(definition)
def docker_login_with_token(url, access_token): '''Configure docker with existing OAuth2 access token''' config_paths = list( map(os.path.expanduser, ['~/.docker/config.json', '~/.dockercfg'])) for path in config_paths: try: with open(path) as fd: dockercfg = yaml.safe_load(fd) if path.endswith('.dockercfg'): dockercfg = {'auths': dockercfg} except: dockercfg = {} if dockercfg: break basic_auth = codecs.encode( 'oauth2:{}'.format(access_token).encode('utf-8'), 'base64').strip().decode('utf-8') if 'auths' not in dockercfg: dockercfg['auths'] = {} dockercfg['auths'][url] = { 'auth': basic_auth, 'email': '*****@*****.**' } for path in config_paths: with Action( 'Storing Docker client configuration in {}..'.format(path)): os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, 'w') as fd: if path.endswith('.dockercfg'): # old config file format json.dump(dockercfg['auths'], fd) else: json.dump(dockercfg, fd)
def target_update(obj, target_uri, sli_name, target_from, target_to, target_file): """Update Target for a product SLO.""" client = get_client(obj) target = client.target_get(target_uri) if not target: fatal_error('Target {} does not exist'.format(target_uri)) product = client.product_list(name=target['product_name'])[0] sli = client.sli_list(product=product, name=sli_name) if not sli: fatal_error('SLI {} does not exist'.format(sli_name)) sli = sli[0] with Action('Updating Target {} for product {}'.format( target_uri, target['product_name']), nl=True) as act: if target_file: target = json.load(target_file) else: if sli_name: target['sli_uri'] = sli['uri'] if target_from: target['from'] = target_from if target_to: target['to'] = target_to validate_target(target, act) if not act.errors: target = client.target_update(target) print(json.dumps(target, indent=4))
def login(obj, profile, refresh, awsprofile): '''Login with given profile(s)''' repeat = True while repeat: last_update = get_last_update(obj) if 'profile' in last_update and last_update['profile'] and not profile: profile = [last_update['profile']] for prof in profile: if prof not in obj['config']: raise click.UsageError('Profile "{}" does not exist'.format(prof)) login_with_profile(obj, prof, obj['config'][prof], awsprofile) if refresh: last_update = get_last_update(obj) wait_time = 3600 * 0.9 with Action('Waiting {} minutes before refreshing credentials..' .format(round(((last_update['timestamp']+wait_time)-time.time()) / 60))) as act: while time.time() < last_update['timestamp'] + wait_time: try: time.sleep(120) except KeyboardInterrupt: # do not show "EXCEPTION OCCURRED" for CTRL+C repeat = False break act.progress() else: repeat = False
def docker_login_with_token(url, access_token): '''Configure docker with existing OAuth2 access token''' path = os.path.expanduser('~/.docker/config.json') try: with open(path) as fd: dockercfg = json.load(fd) except Exception as e: dockercfg = {} basic_auth = codecs.encode( 'oauth2:{}'.format(access_token).encode('utf-8'), 'base64').strip().decode('utf-8') if 'auths' not in dockercfg: dockercfg['auths'] = {} if 'credsStore' in dockercfg: del dockercfg['credsStore'] dockercfg['auths'][url] = { 'auth': basic_auth, 'email': '*****@*****.**' } with Action('Storing Docker client configuration in {}..'.format(path)): os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, 'w') as fd: json.dump(dockercfg, fd)
def get_tv_token(obj): """Retrieve a new token""" client = get_client(obj.config) with Action('Retrieving new one-time token ...', nl=True): token = client.get_onetime_token() ok(client.token_login_url(token.strip('"')))
def create(obj, profile_name, url, user): '''Create a new profile''' if not url.startswith('http'): url = 'https://{}'.format(url) saml_xml, roles = saml_login(user, url) if not roles: error('No roles found') exit(1) if len(roles) == 1: role = roles[0] if role[2] is None: role = (role[0], role[1], profile_name) else: role = choice('Please select one role', [(r, get_role_label(r)) for r in sorted(roles)]) data = obj['config'] if not data: data = {} data[profile_name] = { 'saml_identity_provider_url': url, 'saml_role': role, 'saml_user': user } path = obj['config-file'] with Action('Storing new profile in {}..'.format(path)): os.makedirs(obj['config-dir'], exist_ok=True) with open(path, 'w') as fd: yaml.safe_dump(data, fd)