Source code for cf_api.deploy_blue_green
import os
from deploy_manifest import log
from . import exceptions as exc
[docs]class BlueGreen(object):
"""This class orchestrates a Blue-Green deployment in the style of the
Autopilot CF CLI plugin.
"""
def __init__(self,
space,
manifest,
verbose=True,
wait_kwargs=None,
**kwargs):
"""Initializes the deployment
Args:
space (cf_api.deploy_space.Space):
The space to which the application should be deployed
manifest (cf_api.deploy_manifest.Deploy):
The manifest of the application to be deployed
verbose (bool):
Whether the deployment should be verbose in its output
wait_kwargs (dict|None):
Arguments to pass to the application ``wait_for_app_start``
function when waiting for the application to start
"""
self.space = space
self.manifest = manifest
self.verbose = verbose
self.venerable_name = '-'.join([self.app_name, 'venerable'])
self.venerable_manifest = self.manifest.clone(self.venerable_name)
self.app = None
self.venerable_app = None
self.wait_kwargs = wait_kwargs or {}
@property
def cc(self):
return self.space.cc
@property
def app_name(self):
return self.manifest.name
[docs] @classmethod
def parse_manifest(cls, space, manifest_filename, **kwargs):
"""Parses a deployment manifest and creates a BlueGreen instance
for each application in the manifest.
Args:
space (cf_api.deploy_space.Space):
space to which the manifest should be deployed
manifest_filename (str):
application manifest to be deployed
**kwargs (dict):
passed into the BlueGreen constructor
Returns:
list[BlueGreen]
"""
space.set_debug(kwargs.get('verbose'))
manifests = space.get_deploy_manifest(manifest_filename)
return [BlueGreen(space, manifest, **kwargs) for manifest in manifests]
def log(self, *args):
if self.verbose:
return log(*args)
def _load_apps(self):
self._load_app()
self._load_venerable_app()
def _load_app(self):
try:
self.app = self.space.get_app_by_name(self.app_name)
except exc.ResponseException as e:
self.app = None
if 404 != e.code:
raise
def _load_venerable_app(self):
try:
self.venerable_app = self.space.get_app_by_name(
self.venerable_name)
except exc.ResponseException as e:
self.venerable_app = None
if 404 != e.code:
raise
def _rename_app(self):
self._load_app()
if self.app:
self._load_venerable_app()
if self.venerable_app:
raise exc.InvalidStateException(
'attempting to rename app to venerable, but venerable '
'already exists', 409)
return self.cc.apps(self.app.guid)\
.set_params(name=self.venerable_name).put().data
self._load_apps()
return None
def _destroy_venerable_app(self):
self._load_venerable_app()
if self.venerable_app:
self._load_app()
if not self.app:
raise exc.InvalidStateException(
'attempting to destroy venerable app, but no app will take'
' it\'s place! aborting...', 409)
return self.venerable_manifest.destroy(destroy_routes=False)
self._load_apps()
return None
[docs] def wait_and_cleanup(self):
"""Waits for the new application to start and then destroys the old
version of the app.
"""
self.log('Waiting for app to start...')
self.manifest.wait_for_app_start(
tailing=self.verbose, **self.wait_kwargs)
self.log('OK')
self.log('Destroying venerable...')
self._load_venerable_app()
if self.venerable_app:
self._destroy_venerable_app()
self.log('OK')
[docs] def deploy_app(self):
"""Deploys the new application
"""
self.log('Checking apps...')
self._load_apps()
self.log('OK')
if self.venerable_app:
if self.app:
self.log('Leftover venerable detected with replacement! '
'Deleting...')
self._destroy_venerable_app()
self.log('OK')
else:
self.log('Leftover venerable detected with no replacement! '
'Aborting...')
raise exc.InvalidStateException(
'Leftover venerable detected! Rename it and try again.',
409)
if self.app:
self.log('Renaming app to venerable...')
self._rename_app()
self.log('OK')
self.log('Pushing app...')
self.manifest.push()
self.log('OK')
[docs] def deploy(self):
"""Deploy the new application, wait for it to start, then clean up the
old application.
"""
self.deploy_app()
self.wait_and_cleanup()
def main():
import argparse
from getpass import getpass
from deploy_space import Space
import __init__ as cf_api
args = argparse.ArgumentParser()
args.add_argument('--cloud-controller', required=True)
args.add_argument('-u', '--user')
args.add_argument('-o', '--org', required=True)
args.add_argument('-s', '--space', required=True)
args.add_argument('-m', '--manifest', required=True)
args = args.parse_args()
kwargs = dict(
client_id='cf',
client_secret='',
)
if args.user:
kwargs['username'] = args.user
kwargs['password'] = getpass()
else:
kwargs['refresh_token'] = os.getenv('REFRESH_TOKEN', '')
cc = cf_api.new_cloud_controller(args.cloud_controller, **kwargs)
space = Space(cc, org_name=args.org, space_name=args.space)
manifests = BlueGreen.parse_manifest(
space, args.manifest, verbose=True, wait_kwargs=dict(interval=5))
for manifest in manifests:
manifest.deploy()
if '__main__' == __name__:
main()