def load(self): output.section("Preparing") output.step("main", "Loading environment `{}`...".format(self.environment)) self.environment = Environment(self.environment, self.timeout, self.platform) self.environment.deployment = self self.environment.load() if self.jobs is not None: self.jobs = self.jobs elif self.environment.jobs is not None: self.jobs = int(self.environment.jobs) else: self.jobs = 1 output.step("main", "Number of jobs: %s" % self.jobs, debug=True) # This is located here to avoid duplicating the verification check # when loading the repository on the remote environment object. output.step("main", "Verifying repository ...") self.environment.repository.verify() output.step("main", "Loading secrets ...") self.environment.load_secrets()
def main(environment, platform, timeout, dirty, consistency_only, predict_only, jobs): output.backend = TerminalBackend() output.line(self_id()) STEPS = ['load', 'connect', 'configure', 'deploy'] if consistency_only: ACTION = "CONSISTENCY CHECK" STEPS.remove('deploy') elif predict_only: ACTION = "DEPLOYMENT PREDICTION" else: ACTION = "DEPLOYMENT" with locked(".batou-lock"): deployment = Deployment(environment, platform, timeout, dirty, jobs, predict_only) environment = deployment.environment try: for step in STEPS: try: getattr(deployment, step)() except Exception as e: environment.exceptions.append(e) if not environment.exceptions: continue # Note: There is a similar sorting / output routine in # remote_core __channelexec__. This is a bit of copy/paste # due to the way bootstrapping works environment.exceptions = list( filter( lambda e: not isinstance(e, SilentConfigurationError), environment.exceptions)) environment.exceptions.sort( key=lambda x: getattr(x, 'sort_key', (-99,))) exception = '' for exception in environment.exceptions: if isinstance(exception, ReportingException): output.line('') exception.report() else: output.line('') output.error("Unexpected exception") tb = traceback.TracebackException.from_exception( exception) for line in tb.format(): output.line('\t' + line.strip(), red=True) summary = "{} FAILED (during {})".format(ACTION, step) output.section(summary, red=True) notify(summary, str(exception)) sys.exit(1) finally: deployment.disconnect() output.section("{} FINISHED".format(ACTION), green=True) notify("{} SUCCEEDED".format(ACTION), environment.name)
def main(environment, platform, timeout, dirty, fast, consistency_only, predict_only, reset): output.backend = TerminalBackend() output.line(self_id()) if consistency_only: ACTION = 'CONSISTENCY CHECK' elif predict_only: ACTION = 'DEPLOYMENT PREDICTION' else: ACTION = 'DEPLOYMENT' with locked('.batou-lock'): try: deployment = Deployment(environment, platform, timeout, dirty, fast, predict_only, reset=reset) deployment.load() deployment.connect() deployment.configure() if not consistency_only: deployment.deploy() deployment.disconnect() except MissingEnvironment as e: e.report() output.section("{} FAILED".format(ACTION), red=True) notify( '{} FAILED'.format(ACTION), 'Configuration for {} encountered an error.'.format( environment)) sys.exit(1) except ConfigurationError: output.section("{} FAILED".format(ACTION), red=True) notify( '{} FAILED'.format(ACTION), 'Configuration for {} encountered an error.'.format( environment)) sys.exit(1) except DeploymentError as e: e.report() output.section("{} FAILED".format(ACTION), red=True) notify('{} FAILED'.format(ACTION), '{} encountered an error.'.format(environment)) sys.exit(1) except Exception: # An unexpected exception happened. Bad. output.error("Unexpected exception", exc_info=sys.exc_info()) output.section("{} FAILED".format(ACTION), red=True) notify('{} FAILED'.format(ACTION), 'Encountered an unexpected exception.') sys.exit(1) else: output.section('{} FINISHED'.format(ACTION), green=True) notify('{} SUCCEEDED'.format(ACTION), environment)
def provision(self): if not self.environment.provisioners: return output.section("Provisioning hosts ...") for host in self.environment.hosts.values(): if host.provisioner: output.step( host.name, "Provisioning with `{}` provisioner. {}".format( host.provisioner.name, "(Rebuild)" if host.provisioner.rebuild else "")) host.provisioner.provision(host)
def load(self): output.section("Preparing") output.step("main", "Loading environment `{}`...".format(self.environment)) self.environment = Environment(self.environment, self.timeout, self.platform) self.environment.deployment = self self.environment.load() # This is located here to avoid duplicating the verification check # when loading the repository on the remote environment object. output.step("main", "Verifying repository ...") self.environment.repository.verify() output.step("main", "Loading secrets ...") self.environment.load_secrets()
def deploy(self): # Wait for all connections to finish output.section("Waiting for remaining connections ...") [c.join() for c in self.connections] if self.predict_only: output.section("Predicting deployment actions") else: output.section("Deploying") # Pick a reference remote (the last we initialised) that will pass us # the order we should be deploying components in. reference_node = [ h for h in list(self.environment.hosts.values()) if not h.ignore ][0] self.loop = asyncio.get_event_loop() self.taskpool = ThreadPoolExecutor(self.jobs) self.loop.set_default_executor(self.taskpool) self._launch_components(reference_node.root_dependencies()) pending = asyncio.Task.all_tasks() while pending: self.loop.run_until_complete(asyncio.gather(*pending)) pending = {t for t in asyncio.Task.all_tasks() if not t.done()}
def deploy(self): # Wait for all connections to finish output.section("Waiting for remaining connections ...") [c.join() for c in self.connections] if self.predict_only: output.section("Predicting deployment actions") else: output.section("Deploying") # Pick a reference remote (the last we initialised) that will pass us # the order we should be deploying components in. reference_node = [ h for h in list(self.environment.hosts.values()) if not h.ignore ][0] self.loop = asyncio.get_event_loop() self.taskpool = ThreadPoolExecutor(self.jobs) self.loop.set_default_executor(self.taskpool) self._launch_components(reference_node.root_dependencies()) # asyncio.Task.all_tasks was removed in Python 3.9 # but the replacement asyncio.all_tasks is only available # for Python 3.7 and upwards # confer https://docs.python.org/3/whatsnew/3.7.html # and https://docs.python.org/3.9/whatsnew/3.9.html if sys.version_info < (3, 7): all_tasks = asyncio.Task.all_tasks else: all_tasks = asyncio.all_tasks get_pending = lambda: {t for t in all_tasks() if not t.done()} pending = get_pending() while pending: self.loop.run_until_complete(asyncio.gather(*pending)) pending = get_pending()
def configure(self): output.section("Configuring model ...") self.connections[0].join()
def connect(self): output.section("Connecting ...") # Consume the connection iterator to establish remaining connections. self.connections = list(self._connections())
def main( environment, platform, timeout, dirty, consistency_only, predict_only, jobs ): output.backend = TerminalBackend() output.line(self_id()) if consistency_only: ACTION = "CONSISTENCY CHECK" elif predict_only: ACTION = "DEPLOYMENT PREDICTION" else: ACTION = "DEPLOYMENT" with locked(".batou-lock"): try: deployment = Deployment( environment, platform, timeout, dirty, jobs, predict_only ) deployment.load() deployment.connect() deployment.configure() if not consistency_only: deployment.deploy() deployment.disconnect() except FileLockedError as e: output.error("File already locked: {}".format(e.filename)) output.section("{} FAILED".format(ACTION), red=True) notify( "{} FAILED".format(ACTION), "File already locked: {}".format(e.filename), ) except MissingEnvironment as e: e.report() output.section("{} FAILED".format(ACTION), red=True) notify( "{} FAILED".format(ACTION), "Configuration for {} encountered an error.".format( environment ), ) sys.exit(1) except ConfigurationError: output.section("{} FAILED".format(ACTION), red=True) notify( "{} FAILED".format(ACTION), "Configuration for {} encountered an error.".format( environment ), ) sys.exit(1) except DeploymentError as e: e.report() output.section("{} FAILED".format(ACTION), red=True) notify( "{} FAILED".format(ACTION), "{} encountered an error.".format(environment), ) sys.exit(1) except Exception: # An unexpected exception happened. Bad. output.error("Unexpected exception", exc_info=sys.exc_info()) output.section("{} FAILED".format(ACTION), red=True) notify( "{} FAILED".format(ACTION), "Encountered an unexpected exception.", ) sys.exit(1) else: output.section("{} FINISHED".format(ACTION), green=True) notify("{} SUCCEEDED".format(ACTION), environment)
def summarize(self): output.section("Summary") for node in list(self.environment.hosts.values()): node.summarize()
def output_migration_step(title: str, text: str) -> None: """Print the information of migration step in a formatted way.""" output.section(title, red=True) output.line(textwrap.dedent(text).replace('\n', ' ')) output.line('')