PKr[GClizzy_client/configuration.py""" Copyright 2015 Zalando SE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from environmental import Str class Configuration: lizzy_url = Str('LIZZY_URL') scopes = Str('LIZZY_SCOPES', 'uid') token_url = Str('OAUTH2_ACCESS_TOKEN_URL') credentials_dir = Str('CREDENTIALS_DIR', '/meta/credentials') PKSGlizzy_client/__init__.pyPKSGJ**lizzy_client/__main__.pyfrom lizzy_client.cli import main main() PKo[G)  lizzy_client/token.pyimport tokens def get_token(url: str, scopes: str, credentials_dir: str) -> dict: """ Get access token info. """ tokens.configure(url=url, dir=credentials_dir) tokens.manage('lizzy', [scopes]) tokens.start() return tokens.get('lizzy') PKp[GYlizzy_client/cli.py""" Copyright 2015 Zalando SE Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from clickclick import Action, FloatRange, OutputFormat, print_table, info, fatal_error from tokens import InvalidCredentialsError import click import dateutil.parser import requests import time from .lizzy import Lizzy from .token import get_token from .configuration import Configuration STYLES = { 'CF:RUNNING': {'fg': 'green'}, 'CF:TERMINATED': {'fg': 'red'}, 'CF:DELETE_COMPLETE': {'fg': 'red'}, 'CF:ROLLBACK_COMPLETE': {'fg': 'red'}, 'CF:CREATE_COMPLETE': {'fg': 'green'}, 'CF:CREATE_FAILED': {'fg': 'red'}, 'CF:CREATE_IN_PROGRESS': {'fg': 'yellow', 'bold': True}, 'CF:DELETE_IN_PROGRESS': {'fg': 'red', 'bold': True}, 'CF:ROLLBACK_IN_PROGRESS': {'fg': 'red', 'bold': True}, 'CF:IN_SERVICE': {'fg': 'green'}, 'CF:OUT_OF_SERVICE': {'fg': 'red'}, 'LIZZY:NEW': {'fg': 'yellow', 'bold': True}, 'LIZZY:CHANGE': {'fg': 'yellow', 'bold': True}, 'LIZZY:DEPLOYING': {'fg': 'yellow', 'bold': True}, 'LIZZY:DEPLOYED': {'fg': 'green'}, 'LIZZY:REMOVED': {'fg': 'red'}} TITLES = { 'creation_time': 'Created', 'logical_resource_id': 'Resource ID', 'launch_time': 'Launched', 'resource_status': 'Status', 'resource_status_reason': 'Status Reason', 'lb_status': 'LB Status', 'private_ip': 'Private IP', 'public_ip': 'Public IP', 'resource_id': 'Resource ID', 'instance_id': 'Instance ID', 'version': 'Ver.'} requests.packages.urllib3.disable_warnings() # Disable the security warnings main = click.Group() output_option = click.option('-o', '--output', type=click.Choice(['text', 'json', 'tsv']), default='text', help='Use alternative output format') watch_option = click.option('-w', '--watch', type=click.IntRange(1, 300), metavar='SECS', help='Auto update the screen every X seconds') def fetch_token(token_url: str, scopes: str, credentials_dir: str) -> str: # TODO fix scopes to be really a list """ Common function to fetch token :return: """ with Action('Fetching authentication token..') as action: try: access_token = get_token(token_url, scopes, credentials_dir) action.progress() except InvalidCredentialsError as e: action.fatal_error(e) return access_token @main.command() @click.option('--keep-stacks', default=0) @click.option('--traffic', default=100) @click.option('--verbose', '-v', is_flag=True) @click.argument('definition') # TODO add definition type like senza @click.argument('image_version') @click.argument('senza_parameters', nargs=-1) def create(definition: str, image_version: str, keep_stacks: str, traffic: str, verbose: bool, senza_parameters: list): senza_parameters = senza_parameters or [] config = Configuration() access_token = fetch_token(config.token_url, config.scopes, config.credentials_dir) lizzy = Lizzy(config.lizzy_url, access_token) with Action('Requesting new stack..') as action: try: stack_id = lizzy.new_stack(image_version, keep_stacks, traffic, definition, senza_parameters) except requests.RequestException as e: action.fatal_error('Deployment failed: {}.'.format(e)) info('Stack ID: {}'.format(stack_id)) with Action('Waiting for new stack...') as action: if verbose: print() # ensure that new states will not be printed on the same line as the action last_state = None for state in lizzy.wait_for_deployment(stack_id): if state != last_state and verbose: click.echo(' {}'.format(state)) else: action.progress() last_state = state if last_state == 'CF:ROLLBACK_COMPLETE': fatal_error('Stack was rollback after deployment. Check you application log for possible reasons.') elif last_state == 'LIZZY:REMOVED': fatal_error('Stack was removed before deployment finished.') elif last_state != 'CF:CREATE_COMPLETE': fatal_error('Deployment failed: {}.'.format(last_state)) info('Deployment Successful') @main.command('list') @click.argument('stack_ref', nargs=-1) @click.option('--all', is_flag=True, help='Show all stacks, including deleted ones') @watch_option @output_option def list_stacks(stack_ref: str, all: bool, watch: int, output: str): """List Lizzy stacks""" config = Configuration() access_token = fetch_token(config.token_url, config.scopes, config.credentials_dir) lizzy = Lizzy(config.lizzy_url, access_token) repeat = True while repeat: try: all_stacks = lizzy.get_stacks() except requests.RequestException as e: fatal_error('Failed to get stacks: {}'.format(e)) if all: stacks = all_stacks else: stacks = [stack for stack in all_stacks if stack['status'] not in ['LIZZY:REMOVED']] if stack_ref: stacks = [stack for stack in stacks if stack['stack_name'] in stack_ref] rows = [] for stack in stacks: creation_time = dateutil.parser.parse(stack['creation_time']) rows.append({'stack_name': stack['stack_name'], 'version': stack['stack_version'], 'image_version': stack['image_version'], 'status': stack['status'], 'creation_time': creation_time.timestamp()}) rows.sort(key=lambda x: (x['stack_name'], x['version'])) with OutputFormat(output): print_table('stack_name version image_version status creation_time'.split(), rows, styles=STYLES, titles=TITLES) if watch: time.sleep(watch) click.clear() else: repeat = False @main.command() @click.argument('stack_name') @click.argument('stack_version') @click.argument('percentage', type=FloatRange(0, 100, clamp=True)) def traffic(stack_name: str, stack_version: str, percentage: int): config = Configuration() access_token = fetch_token(config.token_url, config.scopes, config.credentials_dir) lizzy = Lizzy(config.lizzy_url, access_token) with Action('Requesting traffic change..'): stack_id = '{stack_name}-{stack_version}'.format_map(locals()) lizzy.traffic(stack_id, percentage) @main.command() @click.argument('stack_name') @click.argument('stack_version') def delete(stack_name: str, stack_version: str): config = Configuration() access_token = fetch_token(config.token_url, config.scopes, config.credentials_dir) lizzy = Lizzy(config.lizzy_url, access_token) with Action('Requesting stack deletion..'): stack_id = '{stack_name}-{stack_version}'.format_map(locals()) lizzy.delete(stack_id) PKSG%\lizzy_client/lizzy.py""" Copyright 2015 Zalando SE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json import requests import time FINAL_STATES = ["CF:CREATE_COMPLETE", "CF:CREATE_FAILED", "CF:DELETE_COMPLETE", "CF:DELETE_FAILED", "CF:DELETE_IN_PROGRESS" "CF:ROLLBACK_COMPLETE", "CF:ROLLBACK_FAILED", "CF:ROLLBACK_IN_PROGRESS", "LIZZY:ERROR", "LIZZY:REMOVED"] def make_header(access_token: str): headers = dict() headers['Authorization'] = 'Bearer {}'.format(access_token) headers['Content-type'] = 'application/json' return headers class Lizzy: def __init__(self, base_url: str, access_token: str): self.base_url = base_url self.access_token = access_token @property def stacks_url(self): return "{base_url}/stacks".format(base_url=self.base_url) def delete(self, stack_id: str): url = "{base_url}/stacks/{stack_id}".format(base_url=self.base_url, stack_id=stack_id) header = make_header(self.access_token) request = requests.delete(url, headers=header, verify=False) request.raise_for_status() def get_stack(self, stack_id) -> dict: header = make_header(self.access_token) url = "{base_url}/stacks/{stack_id}".format(base_url=self.base_url, stack_id=stack_id) request = requests.get(url, headers=header, verify=False) request.raise_for_status() return request.json() def get_stacks(self) -> list: header = make_header(self.access_token) url = "{base_url}/stacks".format(base_url=self.base_url) request = requests.get(url, headers=header, verify=False) request.raise_for_status() return request.json() def new_stack(self, image_version, keep_stacks, new_traffic, senza_yaml_path, parameters) -> str: header = make_header(self.access_token) with open(senza_yaml_path) as senza_yaml_file: senza_yaml = senza_yaml_file.read() data = {'image_version': image_version, 'keep_stacks': keep_stacks, 'new_traffic': new_traffic, 'parameters:': parameters, 'senza_yaml': senza_yaml} request = requests.post(self.stacks_url, data=json.dumps(data), headers=header, verify=False) request.raise_for_status() stack_info = request.json() return stack_info['stack_id'] def traffic(self, stack_id: str, percentage: int): url = "{base_url}/stacks/{stack_id}".format(base_url=self.base_url, stack_id=stack_id) data = {"new_traffic": percentage} header = make_header(self.access_token) request = requests.patch(url, data=json.dumps(data), headers=header, verify=False) request.raise_for_status() def wait_for_deployment(self, stack_id: str) -> [str]: retries = 3 while retries: try: stack = self.get_stack(stack_id) status = stack["status"] retries = 3 # reset the number of retries yield status if status in FINAL_STATES: return status except Exception as e: retries -= 1 yield 'Failed to get stack ({retries} retries left): {exception}.'.format(retries=retries, exception=repr(e)) time.sleep(10) PK@\Gz7lizzy_client-0.1.201510280907.dist-info/DESCRIPTION.rstLizzy-client PK@\G9t118lizzy_client-0.1.201510280907.dist-info/entry_points.txt[console_scripts] lizzy = lizzy_client.cli:main PK@\G5lizzy_client-0.1.201510280907.dist-info/metadata.json{"classifiers": ["Programming Language :: Python", "Programming Language :: Python :: 3.4", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Operating System :: OS Independent"], "extensions": {"python.commands": {"wrap_console": {"lizzy": "lizzy_client.cli:main"}}, "python.details": {"contacts": [{"name": "Zalando SE", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/zalando/lizzy-client"}}, "python.exports": {"console_scripts": {"lizzy": "lizzy_client.cli:main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "Apache License Version 2.0", "metadata_version": "2.0", "name": "lizzy-client", "run_requires": [{"requires": ["click", "clickclick (>=0.10)", "environmental", "python-dateutil", "pyyaml", "requests", "stups-tokens"]}], "summary": "Lizzy-client", "test_requires": [{"requires": ["pytest", "pytest-cov"]}], "version": "0.1.201510280907"}PK@\G[ 5lizzy_client-0.1.201510280907.dist-info/top_level.txtlizzy_client PK@\G}\\-lizzy_client-0.1.201510280907.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py3-none-any PK@\G#C0lizzy_client-0.1.201510280907.dist-info/METADATAMetadata-Version: 2.0 Name: lizzy-client Version: 0.1.201510280907 Summary: Lizzy-client Home-page: https://github.com/zalando/lizzy-client Author: Zalando SE Author-email: UNKNOWN License: Apache License Version 2.0 Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.4 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Requires-Dist: click Requires-Dist: clickclick (>=0.10) Requires-Dist: environmental Requires-Dist: python-dateutil Requires-Dist: pyyaml Requires-Dist: requests Requires-Dist: stups-tokens Lizzy-client PK@\Gf .lizzy_client-0.1.201510280907.dist-info/RECORDlizzy_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 lizzy_client/__main__.py,sha256=smTdT-ZUUTEPPawnI80fa_06Gbyaf-SyOBic-tgsZWk,42 lizzy_client/cli.py,sha256=YXxiB50FP2yhmHudgNKfE3n2Wt3xaH8F5kbkbUaTxRY,7406 lizzy_client/configuration.py,sha256=h8q3JR4jx1qFDCCFvqSs6YAMcMBlhB1622ooOaN5MBo,796 lizzy_client/lizzy.py,sha256=XRSfhM--gaNa8HSNJKKKRa3UT79oTpSQFyAxgMuJeaE,4040 lizzy_client/token.py,sha256=KLE9ol50PLAees4hQbu_YwB4j5T9hiWyTB1bfUilwZo,267 lizzy_client-0.1.201510280907.dist-info/DESCRIPTION.rst,sha256=5_M2JeGq-0oT2Zg8C9zk1V9F88Ix_xZ62RFtglmWbgI,15 lizzy_client-0.1.201510280907.dist-info/METADATA,sha256=xubd4p-zS32WFzXeJudNpkAlPklRKTdsWNai0XAZX7Y,668 lizzy_client-0.1.201510280907.dist-info/RECORD,, lizzy_client-0.1.201510280907.dist-info/WHEEL,sha256=zX7PHtH_7K-lEzyK75et0UBa3Bj8egCBMXe1M4gc6SU,92 lizzy_client-0.1.201510280907.dist-info/entry_points.txt,sha256=ZgBdmunWZe-GJG4M0Hv2SIdU1dcdk-qMiCkDcTiKADY,49 lizzy_client-0.1.201510280907.dist-info/metadata.json,sha256=wGhSQmcAn9MeQ4FHGyLEIp39e8jehqNJ0C2ES8g_cRQ,962 lizzy_client-0.1.201510280907.dist-info/top_level.txt,sha256=hGDfGsdtlr0eZiDZPYxLvWN9z5HJ0tgCp-Cb63puzLE,13 PKr[GClizzy_client/configuration.pyPKSGWlizzy_client/__init__.pyPKSGJ**lizzy_client/__main__.pyPKo[G)  lizzy_client/token.pyPKp[GY+lizzy_client/cli.pyPKSG%\J"lizzy_client/lizzy.pyPK@\Gz7E2lizzy_client-0.1.201510280907.dist-info/DESCRIPTION.rstPK@\G9t1182lizzy_client-0.1.201510280907.dist-info/entry_points.txtPK@\G503lizzy_client-0.1.201510280907.dist-info/metadata.jsonPK@\G[ 5E7lizzy_client-0.1.201510280907.dist-info/top_level.txtPK@\G}\\-7lizzy_client-0.1.201510280907.dist-info/WHEELPK@\G#C0L8lizzy_client-0.1.201510280907.dist-info/METADATAPK@\Gf .6;lizzy_client-0.1.201510280907.dist-info/RECORDPK D@