def lookup(appname, *vms): """lookup(appname, *vms) Lookup the addresses for virtual machines in a Ravello application. The *appname* parameter must be the name of the application. After this parameter you may pass one or multiple positional arguments containing the VM names. If the VMs are not specified, all VMs in the application are selected. The first VM argument may also be a sequence, set or mapping containing VM names. The return value is a list of Fabric host strings, and may be directly assigned to *env.hosts*. """ app = cache.get_application(name=appname) if app is None: error.raise_error("Application `{0}` does not exist.", appname) if isinstance(vms, compat.str): vms = [vms] app = cache.get_application(app['id']) hosts = [] for vm in app.get('vms', []): if vms and vm['name'] not in vms: continue host = vm['dynamicMetadata']['externalIp'] if fab.env.ravello_user: host = '{0}@{1}'.format(fab.env.ravello_user, host) hosts.append(host) return hosts
def check(path, check): try: validate.validate_node(manifest, path, check) except validate.ValidationError as e: path = validate.pathref(e[1]) error.raise_error('{0}: {1}: {2}', manifest.get('_filename', '<dict>'), path, e[0])
def start_application(name, wait=True, show_progress=True, timeout=1200): """start_application(name, wait=True, show_progress=True, timeout=1200) Start up an application. The *name* argument must be the name of an application. If *wait* is nonzero, then this function will wait until the application is up and its VMs are accessible via ssh. If *show_progress* is nonzero then a progress bar is shown. The *timeout* argument specifies the timeout in seconds. The default timeout is 20 minutes. Application startup times vary greatly between clouds, and whether or not the application has already been published. This method will start all VMs in the application that are in the 'STOPPED' state. If *wait* is nonzero, then all VMs must either be in the 'STOPPED' state (in which case they will get started), in the 'STARTED' state (in which case there is nothing to do), or in a state that will eventually transition to 'STARTED' state (currently 'STARTING' and 'PUBLISHING'). If a VM is in another state, then no action is taken and an exception is raised, because this call would just timeout without the ability to complete. This function has no return value, and raises an exception in case of an error. """ app = cache.get_application(name=name) if app is None: error.raise_error("Application `{0}` does not exist.", name) app = application.start_application(app) if wait: state = application.get_application_state(app) if state not in application.vm_reuse_states: error.raise_error("Cannot wait for app in state '{0}'.", state) vms = set((vm['name'] for vm in app['vms'])) with env.let(quiet=not show_progress): application.wait_for_application(app, vms, timeout)
def remote_checkout(self, version): repotype = env.manifest['repository']['type'] if not repotype: error.raise_error('Unknown repository type, cannot checkout.') url = env.manifest['repository']['url'] commands = versioncontrol.get_checkout_command(url, version, repotype) super(DeployTask, self).run(commands=commands)
def create_archive(): ravello_dir = util.get_ravello_dir() try: st = os.stat(ravello_dir) except OSError: st = None if st and not stat.S_ISDIR(st.st_mode): error.raise_error("Path `{0}` exists but is not a directory.", ravello_dir) elif st is None: os.mkdir(ravello_dir) distfile = os.path.join(ravello_dir, 'dist.tar.gz') try: st = os.stat(distfile) except OSError: st = None if st and st.st_mtime >= env.start_time: return distfile archive = tarfile.TarFile.open(distfile, 'w:gz') repotype = env.manifest['repository']['type'] files = versioncontrol.walk_repository('.', repotype) for fname in files: if fname.startswith(ravello_dir): continue archive.add(fname, recursive=False) archive.close() return distfile
def create_blueprint(name, bpname=None, wait=True): """create_blueprint(name, bpname=None) Create a blueprint from an application. The *name* argument must be an application whose VMs are either all in the STOPPED or in the STARTED state. The *bpname* argument is the name of the blueprint. If the blueprint name is not specified, a new unique name will be allocated. The return value of this function is the name of the blueprint that was created. """ app = cache.get_application(name=name) if app is None: error.raise_error("Application `{0}` does not exist.", name) state = application.get_application_state(app) if state not in ('STOPPED', 'STARTED'): error.raise_error('Application `{0}` is currently in state {1}.\n' 'Can only save when STOPPED or STARTED.', name, state) if bpname is None: bpname = new_blueprint_name('bp-{0}'.format(name)) bp = application.create_blueprint(bpname, app) if wait: bp = application.wait_until_blueprint_is_in_state(bp, 'DONE') return application.appdef_from_app(bp)
def synchronize_on_task(taskname, timeout=600): """Wait until all instances of ``taskname`` have completed. Returns a dictionary with the shared state of the VMs that were waited for. """ waitfor = set() for vmdef in env.appdef['vms']: if vmdef['name'] not in env.vms: continue for taskdef in vmdef['tasks']: if taskdef['name'] == taskname: waitfor.add(vmdef['name']) state = {} end_time = time.time() + timeout while True: for vmdef in env.appdef['vms']: vmname = vmdef['name'] vmstate = env.shared_state[vmname] # resync if vmstate['exited']: state[vmname] = vmstate break if vmname not in waitfor: continue if taskname in vmstate['completed_tasks']: waitfor.remove(vmname) state[vmname] = vmstate if vmstate['exited'] or not waitfor: break console.debug('Waiting for %s' % repr(waitfor)) time.sleep(5) if time.time() > end_time: error.raise_error("Timeout waiting for task `{0}`.", taskname) return state
def create_application(name=None, blueprint=None, vms=None, cloud=None, region=None, wait=True, show_progress=True): """create_application(name=None, blueprint=None, vms=None, cloud=None, \ region=None, wait=True, show_progress=True) Create a new application. If *blueprint* is specified, then it must be the name of a blueprint from which the application is created. If *blueprint* is not specified, then an application will be created from scratch in which case *vms* needs to be specified containing a list of the VM definitions. The VM definitions are dictionaries containing string keys describing the VM. The arguments *cloud* and *region* specify which cloud and region to publish the application to. If these are not specified, the application is published to the lowest cost cloud that fits the VM definitions. If *wait* is nonzero, then this function will wait until the application is started up and its VMs are accessible via ssh. If *show_progress* is nonzero, then a progress bar is shown. The return value of this function is the application definition of the application that was created. In case of an error, an exception is raised. .. seealso:: See :ref:`vm-ref` for the possible keys in a VM definition dictionary. """ if blueprint: bp = cache.get_blueprint(name=blueprint) if bp is None: error.raise_error('Blueprint `{0}` not found.', blueprint) if name is None: name = new_application_name(bp['name']) else: if name is None: name = new_application_name() appdef = { 'name': name } if vms: appdef['vms'] = vms if blueprint: appdef['blueprint'] = blueprint manif = { 'applications': [appdef], 'defaults': { 'vms': { 'smp': 1, 'memory': 2048 } } } manifest.check_manifest(manif) manifest.percolate_defaults(manif) manifest.check_manifest_entities(manif) app = application.create_new_application(appdef, False) app = application.publish_application(app, cloud, region) if wait: vms = set((vm['name'] for vm in app['vms'])) with env.let(quiet=not show_progress): app = application.wait_for_application(app, vms) return application.appdef_from_app(app)
def default_application(appname): """The default application loading function.""" parts = appname.split(':') if len(parts) in (1, 2) and env.manifest is None: error.raise_error('No manifest found ({0}).\n' 'Please specify the fully qualified app name.\n' "Use 'ravtest ps -a' for a list.", manifest.manifest_name()) if len(parts) in (1, 2): project = env.manifest['project']['name'] console.info('Project name is `{0}`.', project) defname = parts[0] instance = parts[1] if len(parts) == 2 else None elif len(parts) == 3: project, defname, instance = parts else: error.raise_error('Illegal application name: `{0}`.', appname) apps = cache.find_applications(project, defname, instance) if len(apps) == 0: error.raise_error('No instances of application `{0}` exist.', defname) elif len(apps) > 1: error.raise_error('Multiple instances of `{0}` exist.\n' 'Use `ravtest ps` to list the instances and then\n' 'specify the application with its instance id.', defname) app = cache.get_application(apps[0]['id']) return app
def create_keypair(): """Create a new keypair and upload it to Ravello.""" cfgdir = util.get_config_dir() privname = os.path.join(cfgdir, 'id_ravello') pubname = privname + '.pub' keyname = 'ravello@%s' % socket.gethostname() # Prefer to generate the key locallly with ssh-keygen because # that gives us more privacy. If ssh-keygen is not available, ask # for a key through the API. sshkeygen = util.which('ssh-keygen') if sshkeygen: try: console.info("Generating keypair using 'ssh-keygen'...") subprocess.call(['ssh-keygen', '-q', '-t', 'rsa', '-C', keyname, '-b', '2048', '-N', '', '-f', privname]) except subprocess.CalledProcessError as e: error.raise_error('ssh-keygen returned with error status {0}', e.returncode) with file(pubname) as fin: pubkey = fin.read() keyparts = pubkey.strip().split() else: keyname = 'ravello@api-generated' console.info('Requesting a new keypair via the API...') keypair = env.api.create_keypair() with file(privname, 'w') as fout: fout.write(keypair['privateKey']) with file(pubname, 'w') as fout: fout.write(keypair['publicKey'].rstrip()) fout.write(' {0} (generated remotely)\n'.format(keyname)) pubkey = keypair['publicKey'].rstrip() keyparts = pubkey.split() keyparts[2:] = [keyname] # Create the pubkey in the API under a unique name pubkeys = env.api.get_pubkeys() keyname = util.get_unused_name(keyname, pubkeys) keyparts[2] = keyname keydata = '{0} {1} {2}\n'.format(*keyparts) pubkey = {'name': keyname} pubkey['publicKey'] = keydata pubkey = env.api.create_pubkey(pubkey) with file(pubname, 'w') as fout: fout.write(keydata) env.public_key = pubkey env.private_key_file = privname return pubkey
def wait_until_blueprint_is_in_state(bp, state, timeout=None, poll_timeout=None): """Wait until a blueprint is in a given state.""" if timeout is None: timeout = 300 if poll_timeout is None: poll_timeout = 5 end_time = time.time() + timeout while True: if time.time() > end_time: break poll_end_time = time.time() + poll_timeout bp = cache.get_blueprint(bp['id'], force_reload=True) bpstate = get_blueprint_state(bp) if bpstate == state: return bp time.sleep(max(0, poll_end_time - time.time())) error.raise_error("Blueprint `{0}` did not reach state '{1}' within " "{2} seconds.", bp['name'], state, timeout) return bp
def do_save(args, env): """The "ravello save" command.""" with env.let(quiet=True): login.default_login() keypair.default_keypair() manif = manifest.default_manifest(required=False) app = application.default_application(args.application) appname = app["name"] project, defname, instance = appname.split(":") state = application.get_application_state(app) if state not in ("STOPPED", "STARTED"): error.raise_error( "Application `{0}:{1}` is currently in state {2}.\n" "Can only create blueprint when STOPPED or STARTED.", defname, instance, state, ) if state == "STARTED" and not env.always_confirm: console.info("Application `{0}:{1}` is currently running.", defname, instance) result = console.confirm("Do you want to continue with a live snapshot") if not result: console.info("Not confirmed.") return error.EX_OK template = "{0}:{1}".format(project, defname) bpname = util.get_unused_name(template, cache.get_blueprints()) parts = bpname.split(":") console.info("Saving blueprint as `{0}:{1}`.", parts[1], parts[2]) blueprint = env.api.create_blueprint(bpname, app) env.blueprint = blueprint # for tests console.info("Blueprint creation process started.") console.info("Use 'ravtest ps -b' to monitor progress.") return error.EX_OK
def wait_until_application_is_in_state(app, state, timeout=None, poll_timeout=None): """Wait until an application is in a given state.""" if timeout is None: timeout = 900 if poll_timeout is None: poll_timeout = 10 end_time = time.time() + timeout while True: if time.time() > end_time: break poll_end_time = time.time() + poll_timeout app = cache.get_application(app['id'], force_reload=True) appstate = get_application_state(app) if appstate == state: return app console.show_progress(appstate[0]) time.sleep(max(0, poll_end_time - time.time())) error.raise_error("Application `{0}` did not reach state '{1}' within " "{2} seconds.", app['name'], state, timeout) return app
def load_manifest(filename=None): """Load the project manifest, merge in the default manifest, and return the result.""" if filename is None: filename = manifest_name() if not manifest_exists(filename): error.raise_error('Project manifest ({0}) not found.', filename) with file(filename) as fin: try: manifest = yaml.load(fin) except yaml.error.YAMLError as e: if env.verbose: error.raise_error('Illegal YAML in manifest.\n' 'Message from parser: {!s}', e) else: error.raise_error('Illegal YAML in manifest.\n' 'Try --verbose for more information.') manifest['_filename'] = filename directory, _ = os.path.split(os.path.abspath(filename)) _, project = os.path.split(directory) manifest['_directory'] = directory packagedir = testmill.packagedir() filename = os.path.join(packagedir, 'defaults.yml') with file(filename) as fin: defaults = yaml.load(fin) merge(defaults, manifest) env.manifest = manifest return manifest
def load_keypair(): """Try to load a keypair that exists in ~/.ravello.""" cfgdir = util.get_config_dir() privname = os.path.join(cfgdir, 'id_ravello') try: st = os.stat(privname) except OSError: return if not stat.S_ISREG(st.st_mode): error.raise_error("Private key {0} is not a regular file.", pivname) pubname = privname + '.pub' try: st = os.stat(pubname) except OSError: st = None if st is None: error.raise_error("Public key {0} does not exist.", pubname) elif not stat.S_ISREG(st.st_mode): error.raise_error("Public key {0} is not a regular file.", pubname) with file(pubname) as fin: pubkey = fin.read() keyparts = pubkey.strip().split() pubkeys = env.api.get_pubkeys() for pubkey in pubkeys: if pubkey['name'] == keyparts[2]: env.public_key = pubkey env.private_key_file = privname return pubkey
def stop_application(name, wait=True, timeout=300): """stop_application(name) Stop an application with name *name*. This method will stop all VMs in the application that are currently in the 'STARTED' state. VMs other states are not touched. The application may not be fully stopped even after this call. For example, if a VM is in the 'STARTING' state, it will eventually transition to 'STARTED'. But a 'STARTNG' VM cannot be stopped before it reaches the 'STARTED' state. """ app = cache.get_application(name=name) if app is None: error.raise_error("Application `{0}` does not exist.", name) app = application.stop_application(app) if wait: state = application.get_application_state(app) if state not in ('STARTED', 'STOPPING', 'STOPPED'): error.raise_error("Cannot wait for app in state '{0}',", state) application.wait_until_application_is_in_state(app, 'STOPPED', timeout)
def do_run(args, env): """The "ravello run" command.""" login.default_login() keypair.default_keypair() manif = manifest.default_manifest() appname = args.application for appdef in manif.get('applications', []): if appdef['name'] == appname: break else: error.raise_error("Unknown application `{0}`.", appname) vms = set((vm['name'] for vm in appdef.get('vms', []))) if args.vms: only = set((name for name in args.vms.split(','))) if not only <= vms: unknown = [name for name in only if name not in vms] what = inflect.plural_noun('virtual machine', len(unknown)) error.raise_error("Unknown {0}: {1}", ', '.join(unknown), what) vms = [name for name in vms if name in only] if not vms: error.raise_error('No virtual machines in application.') app = application.create_or_reuse_application(appdef, args.new) app = application.wait_for_application(app, vms) if args.command: for vm in appdef['vms']: for task in vm['tasks']: if task['name'] == 'execute': task['commands'] = [args.command] elif args.dry_run: for vm in appdef['vms']: vm['tasks'] = [] ret = tasks.run_all_tasks(app, vms) console.info('\n== The following services will be available for {0} ' 'minutes:\n', appdef['keepalive']) for vm in app['vms']: if vm['name'] not in vms: continue svcs = vm.get('suppliedServices') if not svcs: continue console.info('On virtual machine `{0}`:', vm['name']) for svc in svcs: svc = svc['baseService'] addr = util.format_service(vm, svc) console.info(' * {0}: {1}', svc['name'], addr) console.info('') return error.EX_OK if ret == 0 else error.EX_SOFTWARE
def do_ssh(args, env): """The "ravello ssh" command.""" with env.let(quiet=True): login.default_login() keypair.default_keypair() if manifest.manifest_exists(): with env.let(quiet=True): manif = manifest.default_manifest() else: manif = None parts = args.application.split(':') if len(parts) in (1, 2) and manif is None: error.raise_error('No manifest found ({0}).\n' 'Please specify the fully qualified app name.\n' 'Use `ravtest ps --all` for a list.', manifest.manifest_name()) if len(parts) in (1, 2): project = manif['project']['name'] console.info('Project name is `{0}`.', project) defname = parts[0] instance = parts[1] if len(parts) == 2 else None elif len(parts) == 3: project, defname, instance = parts else: error.raise_error('Illegal application name: `{0}`.', appname) apps = cache.find_applications(project, defname, instance) if len(apps) == 0: error.raise_error('No instances of application `{0}` exist.', defname) elif len(apps) > 1: error.raise_error('Multiple instances of `{0}` exist.\n' 'Use `ravtest ps` to list the instances and then\n' 'specify the application with its instance id.', defname) app = cache.get_application(apps[0]['id']) appname = app['name'] _, _, instance = appname.split(':') vmname = args.vm vm = application.get_vm(app, vmname) if vm is None: error.raise_error('Application `{0}:{1}` has no VM named `{2}`.\n' 'Use `ravtest ps --full` to see a list of VMs.', defname, instance, vmname) console.info("Connecting to VM `{0}` of application `{1}:{2}`...", vmname, defname, instance) # Start up the application and wait for it if we need to. state = application.get_application_state(app) if state not in ('PUBLISHING', 'STARTING', 'STOPPED', 'STARTED'): error.raise_error("VM `{0}` is in an unknown state.", vmname) userdata = vm.get('customVmConfigurationData', {}) vmkey = userdata.get('keypair', {}) if vmkey.get('id') != env.public_key['id']: error.raise_error("VM uses unknown public key `{0}`.", vmkey.get('name')) application.start_application(app) application.wait_for_application(app, [vmname]) # Now run ssh. Prefer openssh but fall back to using Fabric/Paramiko. host = 'ravello@{0}'.format(vm['dynamicMetadata']['externalIp']) command = '~/bin/run {0}'.format(args.testid) openssh = util.find_openssh() interactive = os.isatty(sys.stdin.fileno()) if interactive and openssh: if not sys.platform.startswith('win'): # On Unix use execve(). This is the most efficient. argv = ['ssh', '-i', env.private_key_file, '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'LogLevel=quiet', '-t', host, command] console.debug('Starting {0}', ' '.join(argv)) os.execve(openssh, argv, os.environ) else: # Windows has execve() but for some reason it does not work # well with arguments with spaces in it. So use subprocess # instead. command = [openssh, '-i', env.private_key_file, '-o', 'UserKnownHostsFile=NUL', '-o', 'StrictHostKeyChecking=no', '-o', 'LogLevel=quiet', '-t', host, command] ssh = subprocess.Popen(command) ret = ssh.wait() error.exit(ret) # TODO: should also support PuTTY on Windows console.info(textwrap.dedent("""\ Warning: no local openssh installation found. Falling back to Fabric/Paramiko for an interactive shell. However, please note: * CTRL-C and terminal resize signals may not work. * Output of programs that repaint the screen may be garbled (e.g. progress bars). """)) fab.env.host_string = host fab.env.key_filename = env.private_key_file fab.env.disable_known_hosts = True fab.env.remote_interrupt = True fab.env.output_prefix = None fabric.state.output.running = None fabric.state.output.status = None ret = fab.run(command, warn_only=True) return ret.return_code
def wait_until_application_accepts_ssh(app, vms, timeout=None, poll_timeout=None): """Wait until an application is reachable by ssh. An application is reachable by SSH if all the VMs that have a public key in their userdata are connect()able on port 22. """ if timeout is None: timeout = 300 if poll_timeout is None: poll_timeout = 5 waitaddrs = set((vm['dynamicMetadata']['externalIp'] for vm in app['vms'] if vm['name'] in vms)) aliveaddrs = set() end_time = time.time() + timeout # For the intricate details on non-blocking connect()'s, see Stevens, # UNIX network programming, volume 1, chapter 16.3 and following. while True: if time.time() > end_time: break waitfds = {} for addr in waitaddrs: sock = socket.socket() sock.setblocking(False) try: sock.connect((addr, 22)) except socket.error as e: if e.errno not in nb_connect_errors: console.debug('connect(): errno {.errno}'.format(e)) continue waitfds[sock.fileno()] = (sock, addr) poll_end_time = time.time() + poll_timeout while True: timeout = poll_end_time - time.time() if timeout < 0: for fd in waitfds: sock, _ = waitfds[fd] sock.close() break try: wfds = list(waitfds) _, wfds, _ = select.select([], wfds, [], timeout) except select.error as e: if e.args[0] == errno.EINTR: continue console.debug('select(): errno {.errno}'.format(e)) raise for fd in wfds: assert fd in waitfds sock, addr = waitfds[fd] try: err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) except socket.error as e: err = e.errno sock.close() if not err: aliveaddrs.add(addr) waitaddrs.remove(addr) del waitfds[fd] if not waitfds: break if not waitaddrs: return console.show_progress('C') # 'C' = Connecting time.sleep(max(0, poll_end_time - time.time())) unreachable = set((vm['name'] for vm in app['vms'] if vm['dynamicMetadata']['externalIp'] in waitaddrs)) noun = inflect.plural_noun('VM', len(unreachable)) vmnames = '`{0}`'.format('`, `'.join(sorted(unreachable))) error.raise_error('{0} `{1}` did not become reachable within {2} seconds.', noun, vmnames, timeout)
def run_all_tasks(app, vms): """Run the runbook for an application ``app``.""" hosts = [] host_info = {} appname = app['name'].split(':')[1] for appdef in env.manifest['applications']: if appdef['name'] == appname: break else: error.raise_error('Application definition not found?') for vm in app['vms']: if vm['name'] not in vms: continue ipaddr = vm['dynamicMetadata']['externalIp'] hosts.append(ipaddr) host_info[ipaddr] = vm['name'] env.test_id = os.urandom(16).encode('hex') console.info('Starting run `{0}`.', env.test_id) env.host_info = host_info env.start_time = int(time.time()) env.lock = multiprocessing.Lock() manager = multiprocessing.Manager() env.shared_state = manager.dict() for vmname in vms: vmstate = {} vmstate['exited'] = False vmstate['completed_tasks'] = {} vmstate['shell_env_update'] = {} env.shared_state[vmname] = vmstate env.appdef = appdef env.application = app env.vms = vms fab.env.user = '******' fab.env.key_filename = env.private_key_file fab.env.disable_known_hosts = True fab.env.remote_interrupt = True fab.env.hosts = hosts fab.env.parallel = True fab.env.output_prefix = env.debug fabric.state.output.running = env.debug fabric.state.output.output = True fabric.state.output.status = env.debug # This is where it all happens... noun = inflect.plural_noun('virtual machine', len(vms)) console.info('Executing tasks on {0} {1}...', len(vms), noun) fabric.tasks.execute(run_tasklist, env) errors = set() for vmname in vms: vmstate = env.shared_state[vmname] for taskname,status in vmstate['completed_tasks'].items(): if status != 0: errors.add('`{0}` on `{1}`'.format(taskname, vmname)) if not errors: console.info('All tasks were executed succesfully!') else: what = inflect.plural_noun('task', len(errors)) errapps = ', '.join(errors) console.error('The following {0} failed: {1}', what, errapps) fabric.network.disconnect_all() return len(errors)
def do_ps(args, env): """The "ravello ps" command.""" with env.let(quiet=True): login.default_login() pubkey = keypair.default_keypair() manif = manifest.default_manifest(required=False) if manif is None and not args.all: error.raise_error('Project manifest ({0}) not found.\n' "Use 'ravtest ps -a' to list all applications.", manifest.manifest_name()) if args.all: project = None else: project = manif['project']['name'] console.info('Project name is `{0}`.', project) if args.blueprint: apps = cache.find_blueprints(project) what = 'blueprint' else: apps = cache.find_applications(project) what = 'application' apps = sorted(apps, key=lambda app: app['name']) objs = inflect.plural_noun(what) console.writeln('Currently available {0}:\n', objs) current_project = None for app in apps: parts = app['name'].split(':') if parts[0] != project and not args.all: continue if args.all and current_project != parts[0]: console.writeln("== Project: `{0}`", parts[0]) current_project = parts[0] if args.full and not args.blueprint: app = cache.get_application(app['id']) cloud = app.get('cloud') region = app.get('regionName') started = app.get('totalStartedVms') publish_time = app.get('publishStartTime') creation_time = app.get('creationTime') created = publish_time or creation_time if created: now = time.time() created = util.format_timedelta(now - created/1000) created = '{0} ago'.format(created) else: created = '' if args.full and not args.blueprint: vms = [ vm['name'] for vm in application.get_vms(app) ] vms = '`{0}`'.format('`, `'.join(vms)) state = application.get_application_state(app) else: state = app.get('state') or '' console.writeln('=== {0}: `{1}:{2}`', what.title(), parts[1], parts[2]) what2 = inflect.plural_noun('VM', started) if state: console.writeln(' state: {0}', state) if started is not None: console.writeln(' {0} {1} running', started, what2) if cloud is not None: console.writeln(' published to {0}/{1}', cloud, region) if created: console.writeln(' created: {0}', created) if args.full and not args.blueprint: console.writeln(' VMs: {0}', vms) console.writeln() return error.EX_OK