class ReportHooks(TaskHook): def __init__(self): self.manifest = JoltManifest() def finalize_report(self, report, task, result): report.name = task.qualified_name report.duration = str(task.duration_running.seconds) report.goal = str(task.is_goal()).lower() report.identity = task.identity report.result = result if hasattr(task, "logstash"): report.logstash = task.logstash self.manifest.append(report) @contextmanager def task_run(self, task): try: yield except Exception as e: with task.task.report() as report: if not report.errors: report.add_exception(e) self.finalize_report(report.manifest, task, "FAILED") raise e else: with task.task.report() as report: self.finalize_report(report.manifest, task, "SUCCESS") def write(self, filename): self.manifest.write(filename) @contextmanager def update(self): yield self.manifest
def _autocomplete_tasks(ctx, args, incomplete): manifest = JoltManifest() utils.call_and_catch(manifest.parse) manifest.process_import() tasks = JoltLoader.get().load() tasks = [ task.name for task in tasks if task.name.startswith(incomplete or '') ] return sorted(tasks)
def get_parameters(self, task): registry = TaskRegistry() registry.add_task_class(Jolt) acache = ArtifactCache.get() env = JoltEnvironment(cache=acache) gb = GraphBuilder(registry, JoltManifest()) dag = gb.build(["jolt"]) task = dag.select(lambda graph, task: True) assert len(task) == 1, "too many selfdeploy tasks found" task = task[0] if not acache.is_available_remotely(task): factory = LocalExecutorFactory() executor = LocalExecutor(factory, task, force_upload=True) executor.run(env) jolt_url = acache.location(task) raise_error_if(not jolt_url, "failed to deploy jolt to a remote cache") return { "jolt_url": jolt_url, "jolt_identity": task.identity[:8], "jolt_requires": config.get("selfdeploy", "requires", "") }
def _create_manifest(self): manifest = JoltManifest.export(self.task) build = manifest.create_build() tasks = [self.task.qualified_name] tasks += [t.qualified_name for t in self.task.extensions] for task in tasks: mt = build.create_task() mt.name = task registry = scheduler.ExecutorRegistry.get() for key, value in registry.get_network_parameters(self.task).items(): param = manifest.create_parameter() param.key = key param.value = value routing_key = WorkerTaskConsumer.ROUTING_KEY_PREFIX routing_key += getattr(self.task.task, "routing_key", WorkerTaskConsumer.ROUTING_KEY_REQUEST) return manifest.format(), routing_key
def selfdeploy(self): """ Installs the correct version of Jolt as specified in execution request. """ tools = Tools() manifest = JoltManifest() try: manifest.parse() ident = manifest.get_parameter("jolt_identity") url = manifest.get_parameter("jolt_url") if not ident or not url: return "jolt" requires = manifest.get_parameter("jolt_requires") log.info("Jolt version: {}", ident) src = "build/selfdeploy/{}/src".format(ident) env = "build/selfdeploy/{}/env".format(ident) if not fs.path.exists(env): try: fs.makedirs(src) tools.run("curl {} | tar zx -C {}", url, src) tools.run("virtualenv {}", env) tools.run(". {}/bin/activate && pip install -e {}", env, src) if requires: tools.run( ". {}/bin/activate && pip install {}", env, requires) except Exception as e: tools.rmtree("build/selfdeploy/{}", ident, ignore_errors=True) raise e return ". {}/bin/activate && jolt".format(env) except Exception as e: log.exception() raise e
def cli(ctx, verbose, extra_verbose, config_file, debugger, profile, force, salt, debug, network, local, keep_going, jobs): """ A task execution tool. When invoked without any commands and arguments, Jolt by default tries to execute and build the artifact of a task called `default`. To build artifacts of other tasks use the build subcommand. The Jolt command line interface is hierarchical. One set of options can be passed to the top-level command and a different set of options to the subcommands, simultaneously. For example, verbose output is a top-level option while forced rebuild is a build command option. They may combined like this: $ jolt --verbose build --force taskname Most build command options are available also at the top-level when build is invoked implicitly for the default task. """ global debug_enabled debug_enabled = debugger log.verbose("Jolt command: {}", " ".join([fs.path.basename(sys.argv[0])] + sys.argv[1:])) log.verbose("Jolt host: {}", environ.get("HOSTNAME", "localhost")) log.verbose("Jolt install path: {}", fs.path.dirname(__file__)) if ctx.invoked_subcommand in ["config"]: # Don't attempt to load any task recipes as they might require # plugins that are not yet configured. return if ctx.invoked_subcommand is None: build = ctx.command.get_command(ctx, "build") manifest = JoltManifest() utils.call_and_catch(manifest.parse) manifest.process_import() ctx.obj["manifest"] = manifest if manifest.version: from jolt.version_utils import requirement, version req = requirement(manifest.version) ver = version(__version__) raise_error_if(not req.satisfied(ver), "this project requires Jolt version {} (running {})", req, __version__) loader = JoltLoader.get() tasks = loader.load() for cls in tasks: TaskRegistry.get().add_task_class(cls) if ctx.invoked_subcommand in ["build", "clean"] and loader.joltdir: ctx.obj["workspace_lock"] = utils.LockFile( fs.path.join(loader.joltdir, "build"), log.info, "Workspace is locked by another process, please wait...") atexit.register(ctx.obj["workspace_lock"].close) # If no command is given, we default to building the default task. # If the default task doesn't exist, help is printed inside build(). if ctx.invoked_subcommand is None: task = config.get("jolt", "default", "default") taskname, _ = utils.parse_task_name(task) if TaskRegistry.get().get_task_class(taskname) is not None: ctx.invoke(build, task=[task], force=force, salt=salt, debug=debug, network=network, local=local, keep_going=keep_going, jobs=jobs) else: print(cli.get_help(ctx)) sys.exit(1)
def _run(self, env): timeout = int(config.getint("amqp", "timeout", 300)) manifest, routing_key = self._create_manifest() self.connect() self.publish_request(manifest, routing_key) log.debug("[AMQP] Queued {0}", self.task.short_qualified_name) self.task.running() for extension in self.task.extensions: extension.running() while self.response is None: try: self.connection.process_data_events(time_limit=timeout) if self.response is None: self.task.info( "Remote execution still in progress after {}", self.task.duration_queued) except (ConnectionError, AMQPConnectionError): log.warning("[AMQP] Lost server connection") self.connect() log.debug("[AMQP] Finished {0}", self.task.short_qualified_name) manifest = JoltManifest() with raise_task_error_on_exception( self.task, "failed to parse build result manifest"): manifest.parsestring(self.response) self.task.running(utils.duration() - float(manifest.duration)) if manifest.result != "SUCCESS": output = [] if manifest.stdout: output.extend(manifest.stdout.split("\n")) if manifest.stderr: output.extend(manifest.stderr.split("\n")) for line in output: log.transfer(line, self.task.identity[:8]) for task in [self.task] + self.task.extensions: with task.task.report() as report: remote_report = manifest.find_task(task.qualified_name) if remote_report: for error in remote_report.errors: report.manifest.append(error) raise_error("[AMQP] remote build failed with status: {0}".format( manifest.result)) raise_task_error_if( not env.cache.is_available_remotely(self.task), self.task, "no task artifact available in any cache, check configuration") raise_task_error_if( not env.cache.download(self.task) and env.cache.download_enabled(), self.task, "failed to download task artifact") for extension in self.task.extensions: raise_task_error_if( not env.cache.download(extension) and env.cache.download_enabled(), self.task, "failed to download task artifact") return self.task
def run(self): with open("default.joltxmanifest", "wb") as f: f.write(self.body) log.info("Manifest written") tools = Tools() for recipe in tools.glob("*.jolt"): tools.unlink(recipe) try: jolt = self.selfdeploy() config_file = config.get("amqp", "config", "") if config_file: config_file = "-c " + config_file log.info("Running jolt") tools.run( "{} -vv {} build --worker --result result.joltxmanifest", jolt, config_file, output_stdio=True) except JoltCommandError as e: self.response = "" try: manifest = JoltManifest() try: manifest.parse("result.joltxmanifest") except Exception: manifest.duration = "0" manifest.result = "FAILED" manifest.stdout = "\n".join(e.stdout) manifest.stderr = "\n".join(e.stderr) self.response = manifest.format() except Exception: log.exception() log.error("Task failed") except Exception: log.exception() self.response = "" try: manifest = JoltManifest() try: manifest.parse("result.joltxmanifest") except Exception: manifest.duration = "0" manifest.result = "FAILED" self.response = manifest.format() except Exception: log.exception() log.error("Task failed") else: self.response = "" try: manifest = JoltManifest() try: manifest.parse("result.joltxmanifest") except Exception: manifest.duration = "0" manifest.result = "SUCCESS" self.response = manifest.format() except Exception: log.exception() log.info("Task succeeded") utils.call_and_catch(tools.unlink, "result.joltxmanifest") self.consumer.add_on_job_completed_callback(self)
def __init__(self): self.manifest = JoltManifest()