def deploy_compressed_csar( csar_name: str, inputs: typing.Optional[dict], storage: Storage, verbose_mode: bool, num_workers: int, delete_existing_state: bool ): if delete_existing_state: storage.remove("instances") if inputs is None: inputs = {} storage.write_json(inputs, "inputs") csars_dir = Path(storage.path) / "csars" csars_dir.mkdir(exist_ok=True) csar = CloudServiceArchive.create(PurePath(csar_name)) csar.validate_csar() tosca_service_template = csar.get_entrypoint() # unzip csar, save the path to storage and set workdir csar_dir = csars_dir / Path("csar") ZipFile(csar_name, "r").extractall(csar_dir) csar_tosca_service_template_path = csar_dir / tosca_service_template storage.write(str(csar_tosca_service_template_path), "root_file") workdir = str(csar_dir) # initialize service template from CSAR and deploy ast = tosca.load(Path(csar_dir), Path(tosca_service_template)) template = ast.get_template(inputs) topology = template.instantiate(storage) topology.deploy(verbose_mode, workdir, num_workers)
def _update(service_template: str, inputs: typing.Optional[dict], num_workers: int): original_storage = Storage.create() new_storage = Storage.create(instance_path=".opera-api-update") new_storage.write_json(inputs, "inputs") new_storage.write(service_template, "root_file") instance_diff = opera_diff_instances(original_storage, ".", new_storage, ".", TemplateComparer(), InstanceComparer(), True) opera_update(original_storage, ".", new_storage, ".", InstanceComparer(), instance_diff, True, num_workers, overwrite=True)
def outputs(): logger.debug("Entry: outputs") try: opera_storage = Storage.create() result = opera_outputs(opera_storage) except Exception as e: logger.error("Error getting outputs.", e) return {"message": str(e)}, 500 if not result: return {"message": "No outputs exist for this deployment."}, 404 return result, 200
def diff(body: dict = None): logger.debug("Entry: diff") diff_request = DiffRequest.from_dict(body) try: original_storage = Storage.create() original_service_template = original_storage.read("root_file") original_inputs = original_storage.read_json("inputs") with tempfile.TemporaryDirectory(prefix=".opera-api-diff", dir=".") as new_storage_root: new_storage = Storage.create(instance_path=new_storage_root) with tempfile.NamedTemporaryFile(prefix="diff-service-template", dir=".") as new_service_template: new_service_template.write( diff_request.new_service_template_contents) new_service_template.flush() if diff_request.template_only: diff_result = opera_diff_templates( original_service_template, ".", original_inputs, new_service_template.name, ".", diff_request.inputs, TemplateComparer(), True) else: diff_result = opera_diff_instances(original_storage, ".", new_storage, ".", TemplateComparer(), InstanceComparer(), True) result = Diff(added=diff_result.added, changed=diff_result.changed, deleted=diff_result.deleted) except Exception as e: logger.error("Error performing diff.", e) return {"message": str(e)}, 500 return result, 200
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( "Directory {} is not a valid path!".format(args.instance_path)) if args.workers < 1: print("{} is not a positive number!".format(args.workers)) return 1 storage = Storage.create(args.instance_path) status = info(None, storage)["status"] if storage.exists("instances"): if args.resume and status == "error": if not args.force: print( "The resume undeploy option might have unexpected consequences on the already " "undeployed blueprint.") question = prompt_yes_no_question() if not question: return 0 elif status == "initialized": print( "The project is initialized. You have to deploy it first to be able to run undeploy." ) return 0 elif status == "deploying": print( "The project is currently deploying. Please try again after the deployment." ) return 0 elif status == "undeployed": print("All instances have already been undeployed.") return 0 elif status == "error": print( "The instance model already exists. Use --resume/-r option to continue current undeployment process." ) return 0 try: undeploy(storage, args.verbose, args.workers) except ParseError as e: print("{}: {}".format(e.loc, e)) return 1 except DataError as e: print(str(e)) return 1 return 0
def init(csar_initialization_input: CsarInitializationInput): logger.debug("Entry: init") try: opera_storage = Storage.create() opera_init_compressed_csar(".", csar_initialization_input.inputs, opera_storage, csar_initialization_input.clean) return {"success": True, "message": ""}, 200 except Exception as e: return { "success": False, "message": "General error: {}".format(str(e)) }, 500
def initialize_compressed_csar(csar_name: str, inputs: typing.Optional[dict], storage: Storage): if inputs is None: inputs = {} storage.write_json(inputs, "inputs") csars_dir = Path(storage.path) / "csars" csars_dir.mkdir(exist_ok=True) # validate csar csar = CloudServiceArchive(csar_name, csars_dir) tosca_service_template = csar.validate_csar() # unzip csar and save the path to storage csar_dir = csars_dir / Path("csar") ZipFile(csar_name, 'r').extractall(csar_dir) csar_tosca_service_template_path = csar_dir / tosca_service_template storage.write(str(csar_tosca_service_template_path), "root_file") # try to initiate service template from csar ast = tosca.load(Path(csar_dir), Path(tosca_service_template)) template = ast.get_template(inputs) template.instantiate(storage)
def test_undefined_required_properties1(self, tmp_path, yaml_text): name = pathlib.PurePath("template.yaml") (tmp_path / name).write_text( yaml_text( # language=yaml """ tosca_definitions_version: tosca_simple_yaml_1_3 node_types: my_node_type: derived_from: tosca.nodes.Root attributes: test_attribute: type: boolean properties: test_property1: type: integer default: 42 required: false test_property2: type: float default: 42.0 required: true test_property3: type: string required: true topology_template: node_templates: my_node_template: type: my_node_type """)) storage = Storage(tmp_path / pathlib.Path(".opera")) storage.write("template.yaml", "root_file") ast = tosca.load(tmp_path, name) with pytest.raises( ParseError, match="Missing a required property: test_property3"): ast.get_template({})
def notify(storage: Storage, verbose_mode: bool, trigger_name_or_event: str, notification_file_contents: typing.Optional[str]): if storage.exists("inputs"): inputs = yaml.safe_load(storage.read("inputs")) else: inputs = {} if storage.exists("root_file"): service_template_path = PurePath(storage.read("root_file")) workdir = Path(service_template_path.parent) if storage.exists("csars"): csar_dir = Path(storage.path) / "csars" / "csar" workdir = csar_dir ast = tosca.load(workdir, service_template_path.relative_to(csar_dir)) else: ast = tosca.load(workdir, PurePath(service_template_path.name)) template = ast.get_template(inputs) # check if specified trigger or event name exists in template if trigger_name_or_event: trigger_name_or_event_exists = False for policy in template.policies: for trigger in policy.triggers.values(): if trigger_name_or_event in (trigger.name, trigger.event.data): trigger_name_or_event_exists = True break if not trigger_name_or_event_exists: raise DataError(f"The provided trigger or event name does not exist: {trigger_name_or_event}.") topology = template.instantiate(storage) topology.notify(verbose_mode, workdir, trigger_name_or_event, notification_file_contents) else: print("There is no root_file in storage.")
def build_image(inv: Invocation): with tempfile.TemporaryDirectory() as workdir: dir_util.copy_tree(Settings.TOSCA_path, workdir) with image_builder_util.cwd(workdir): opera_storage = Storage.create(".opera") service_template = "docker_image_definition.yaml" build_params = transform_build_params(inv.build_params) logger.info(json.dumps(build_params)) opera_deploy(service_template, build_params, opera_storage, verbose_mode=False, num_workers=1, delete_existing_state=True) return opera_outputs(opera_storage)
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( "Directory {0} is not a valid path!".format(args.instance_path)) storage = Storage.create(args.instance_path) try: outs = info(storage) print(format_outputs(outs, args.format)) except ParseError as e: print("{}: {}".format(e.loc, e)) return 1 except DataError as e: print(str(e)) return 1 return 0
def prepare_csar(path, yaml_text, template): # language=yaml tosca_meta = \ """ TOSCA-Meta-File-Version: 1.1 CSAR-Version: 1.1 Created-By: xOpera TOSCA orchestrator Entry-Definitions: service.yaml """ Path.mkdir(path / "TOSCA-Metadata") (path / "TOSCA-Metadata" / "TOSCA.meta").write_text(yaml_text(tosca_meta)) name = PurePath("service.yaml") (path / name).write_text(yaml_text(template)) shutil.make_archive(path / "compressed" / "test", "zip", path) storage = Storage(path / Path(".opera")) return path, storage
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError(f"Directory {args.instance_path} is not a valid path!") storage = Storage.create(args.instance_path) status = info(None, storage)["status"] if not args.force and storage.exists("instances"): if status == "initialized": print("Running notify without previously running deploy might have unexpected consequences.") question = prompt_yes_no_question() if not question: return 0 elif status in ("deploying", "undeploying"): print("The project is in the middle of some other operation. Please try again after some time.") return 0 elif status == "undeployed": print("Running notify in an undeployed project might have unexpected consequences.") question = prompt_yes_no_question() if not question: return 0 elif status == "error": print("Running notify after a deployment with an error might have unexpected consequences.") question = prompt_yes_no_question() if not question: return 0 if not args.force and not args.trigger: print("You have not specified which policy trigger to use (with --trigger/-t or --event/-e) " "and in this case all the triggers will be invoked which might not be what you want.") question = prompt_yes_no_question() if not question: return 0 # read the notification file and the pass its contents to the library function notification_file_contents = Path(args.notification.name).read_text(encoding="utf-8") if args.notification else None try: notify(storage, args.verbose, args.trigger, notification_file_contents) except ParseError as e: print(f"{e.loc}: {e}") return 1 except DataError as e: print(str(e)) return 1 return 0
def run_test(registry_ip: str, test_name: str): """ Runs yaml test and returns exit_code """ test_path = test_build_params / f'{test_name}.json' inputs = json_to_yaml(test_path, registry_ip) with cwd(tosca_path): opera_storage = Storage.create('.opera') try: opera_deploy('docker_image_definition.yaml', inputs, opera_storage, verbose_mode=False, num_workers=1, delete_existing_state=True) except OperationError: return 1 finally: shutil.rmtree((tosca_path / ".opera"), ignore_errors=True) return 0
def validate(blueprint_id: str, version_tag: str, inputs: dict): with tempfile.TemporaryDirectory() as location: CSAR_db.get_revision(blueprint_id, location, version_tag) try: with xopera_util.cwd(location): opera_storage = Storage.create(".opera") service_template = Path(location) / entry_definitions( location) opera_validate(service_template, inputs, opera_storage, verbose=False, executors=False) return None except Exception as e: return "{}: {}".format( e.__class__.__name__, xopera_util.mask_workdir(location, str(e)))
def load_invocation(cls, job_id: uuid) -> Optional[Invocation]: # TODO database # try: # inv = SQL_database.get_deployment_status(deployment_id) # # if inv.state == InvocationState.IN_PROGRESS: # # inv.stdout = InvocationWorkerProcess.read_file(cls.stdout_file(inv.deployment_id)) # # inv.stderr = InvocationWorkerProcess.read_file(cls.stderr_file(inv.deployment_id)) # return inv # # except Exception in (FileNotFoundError, AttributeError): # return None storage = Storage.create(".opera-api") filename = "invocation-{}.json".format(job_id) try: dump = storage.read_json(filename) except FileNotFoundError: return None return Invocation.from_dict(dump)
def init_service_template(service_template: str, inputs: typing.Optional[dict], storage: Storage, clean_storage: bool): if storage.exists("root_file"): if clean_storage: storage.remove_all() else: print("Looks like service template or CSAR has already been initialized. " "Use --clean/-c flag to clear the storage.") return if inputs is None: inputs = {} storage.write_json(inputs, "inputs") storage.write(service_template, "root_file") ast = tosca.load(Path.cwd(), PurePath(service_template)) template = ast.get_template(inputs) template.instantiate(storage)
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( f"Directory {args.instance_path} is not a valid path!") storage = Storage.create(args.instance_path) try: outs = outputs(storage) if args.output: save_outputs(outs, args.format, args.output) else: print(format_outputs(outs, args.format).strip()) except ParseError as e: print(f"{e.loc}: {e}") return 1 except DataError as e: print(str(e)) return 1 return 0
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( "Directory {0} is not a valid path!".format(args.instance_path)) if args.workers < 1: print("{0} is not a positive number!".format(args.workers)) return 1 storage = Storage.create(args.instance_path) try: undeploy(storage, args.verbose, args.workers) except ParseError as e: print("{}: {}".format(e.loc, e)) return 1 except DataError as e: print(str(e)) return 1 return 0
def info(csar_or_rootdir: Optional[PurePath], storage: Storage) -> dict: info_dict: Dict[str, Optional[str]] = dict(service_template=None, content_root=None, inputs=None, status=None) # stateless autodetect first if possible, # which can then be overwritten via state if csar_or_rootdir is not None: csar = CloudServiceArchive.create(csar_or_rootdir) try: csar.validate_csar() info_dict["content_root"] = str(csar_or_rootdir) meta = csar.parse_csar_meta() if meta is not None: info_dict["service_template"] = meta.entry_definitions except OperaError: pass if storage.exists("root_file"): service_template = storage.read("root_file") info_dict["service_template"] = service_template if storage.exists("inputs"): info_dict["inputs"] = str(storage.path / "inputs") inputs = yaml.safe_load(storage.read("inputs")) else: inputs = {} if storage.exists("csars/csar"): csar_dir = Path(storage.path) / "csars" / "csar" info_dict["content_root"] = str(csar_dir) ast = tosca.load(Path(csar_dir), PurePath(service_template).relative_to(csar_dir)) else: ast = tosca.load(Path.cwd(), PurePath(service_template)) if storage.exists("instances"): template = ast.get_template(inputs) # We need to instantiate the template in order # to get access to the instance state. topology = template.instantiate(storage) info_dict["status"] = topology.get_info() else: info_dict["status"] = "initialized" return info_dict
def _deploy_fresh(location: Path, inv: ExtendedInvocation): CSAR_db.get_revision(inv.blueprint_id, location, inv.version_id) with xopera_util.cwd(location): try: if inv.user_id and Settings.secure_workdir: xopera_util.setup_user([location], inv.user_id, inv.access_token) opera_storage = Storage.create(".opera") service_template = entry_definitions(location) opera_deploy(service_template, inv.inputs, opera_storage, verbose_mode=False, num_workers=inv.workers, delete_existing_state=True) outputs = opera_outputs(opera_storage) return outputs finally: if inv.user_id and Settings.secure_workdir: xopera_util.cleanup_user()
def validate_new(CSAR: FileStorage, inputs: dict): try: with tempfile.TemporaryDirectory() as location: with tempfile.TemporaryDirectory() as csar_workdir: csar_path = Path(csar_workdir) / Path(CSAR.filename) CSAR.save(Path(csar_path).open('wb')) csar_to_blueprint(csar=csar_path, dst=location) with xopera_util.cwd(location): opera_storage = Storage.create(".opera") service_template = Path(location) / entry_definitions( location) opera_validate(service_template, inputs, opera_storage, verbose=False, executors=False) return None except Exception as e: return "{}: {}".format( e.__class__.__name__, xopera_util.mask_workdirs([location, csar_workdir], str(e)), )
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( "Directory {0} is not a valid path!".format(args.instance_path)) storage = Storage.create(args.instance_path) try: if args.csar_or_rootdir is None: csar_path = "." else: csar_path = args.csar_or_rootdir outs = info(PurePath(csar_path), storage) if args.output: save_outputs(outs, args.format, args.output) else: print(format_outputs(outs, args.format)) except ParseError as e: print("{}: {}".format(e.loc, e)) return 1 except DataError as e: print(str(e)) return 1 return 0
def info(storage: Storage) -> dict: """ :raises ParseError: :raises DataError: """ info_dict = dict(service_template=None, content_root=None, inputs=None, status=None) if storage.exists("root_file"): service_template = storage.read("root_file") info_dict["service_template"] = service_template if storage.exists("inputs"): info_dict["inputs"] = str(storage.path / "inputs") inputs = yaml.safe_load(storage.read("inputs")) else: inputs = {} if storage.exists("csars/csar"): csar_dir = Path(storage.path) / "csars" / "csar" info_dict["content_root"] = str(csar_dir) ast = tosca.load(Path(csar_dir), PurePath(service_template).relative_to(csar_dir)) else: ast = tosca.load(Path.cwd(), PurePath(service_template)) if storage.exists("instances"): template = ast.get_template(inputs) # We need to instantiate the template in order # to get access to the instance state. topology = template.instantiate(storage) info_dict["status"] = topology.get_info() else: info_dict["status"] = "initialized" return info_dict
def info(csar_or_rootdir: Optional[PurePath], storage: Storage) -> dict: # pylint: disable=too-many-statements info_dict: Dict[str, Optional[Union[str, dict, bool]]] = dict( service_template=None, content_root=None, inputs=None, status=None, csar_metadata=None, service_template_metadata=None, csar_valid=None) # stateless autodetect first if possible, # which can then be overwritten via state if csar_or_rootdir is not None: csar = CloudServiceArchive.create(csar_or_rootdir) try: # this validates CSAR and the entrypoint's (if it exists) metadata # failure to validate here means no static information will be available at all csar.validate_csar() info_dict["csar_valid"] = True info_dict["content_root"] = str(csar_or_rootdir) if csar.get_entrypoint() is not None: info_dict["service_template"] = str(csar.get_entrypoint()) try: service_template_meta = csar.parse_service_template_meta( csar.get_entrypoint()) if service_template_meta: info_dict[ "service_template_metadata"] = service_template_meta.to_dict( ) except OperaError: pass try: csar_meta = csar.parse_csar_meta() if csar_meta: info_dict["csar_metadata"] = csar_meta.to_dict() except OperaError: pass except OperaError: # anything that fails because of validation can be ignored, we can use state # we mark the CSAR as invalid because it's useful to know info_dict["csar_valid"] = False # detection from state, overrides stateless if storage.exists("root_file"): service_template_path = PurePath(storage.read("root_file")) info_dict["service_template"] = str(service_template_path) if storage.exists("inputs"): inputs = yaml.safe_load(storage.read("inputs")) else: inputs = {} info_dict["inputs"] = inputs if storage.exists("csars/csar"): csar_dir = Path(storage.path) / "csars" / "csar" info_dict["content_root"] = str(csar_dir) ast = tosca.load(Path(csar_dir), service_template_path.relative_to(csar_dir)) try: csar = CloudServiceArchive.create(csar_dir) csar.validate_csar() info_dict["csar_valid"] = True try: service_template_meta = csar.parse_service_template_meta( csar.get_entrypoint()) if service_template_meta: info_dict[ "service_template_metadata"] = service_template_meta.to_dict( ) except OperaError: pass try: csar_meta = csar.parse_csar_meta() if csar_meta: info_dict["csar_metadata"] = csar_meta.to_dict() except OperaError: pass except OperaError: info_dict["csar_valid"] = False else: ast = tosca.load(Path(service_template_path.parent), PurePath(service_template_path.name)) if storage.exists("instances"): template = ast.get_template(inputs) # We need to instantiate the template in order # to get access to the instance state. topology = template.instantiate(storage) info_dict["status"] = topology.status() else: info_dict["status"] = "initialized" return info_dict
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( f"Directory {args.instance_path} is not a valid path!") storage_old = Storage.create(args.instance_path) comparer = TemplateComparer() if args.template: service_template_new_path = Path(args.template.name) else: print("Template file for comparison was not supplied.") return 1 if storage_old.exists("root_file"): service_template_old_path = Path(storage_old.read("root_file")) else: print("There is no root_file in storage.") return 1 if storage_old.exists("inputs"): inputs_old = storage_old.read_json("inputs") else: inputs_old = {} try: if args.inputs: inputs_new = yaml.safe_load(args.inputs) else: inputs_new = {} except yaml.YAMLError as e: print(f"Invalid inputs: {e}") return 1 workdir_old = get_workdir(storage_old) workdir_new = Path(service_template_new_path.parent) try: if args.template_only: template_diff = diff_templates(service_template_old_path, workdir_old, inputs_old, service_template_new_path, workdir_new, inputs_new, comparer, args.verbose) else: instance_comparer = InstanceComparer() with tempfile.TemporaryDirectory() as temp_path: storage_new = Storage.create(temp_path) storage_new.write_json(inputs_new, "inputs") storage_new.write(str(service_template_new_path), "root_file") template_diff = diff_instances(storage_old, workdir_old, storage_new, workdir_new, comparer, instance_comparer, args.verbose) outputs = template_diff.outputs() if args.output: save_outputs(outputs, args.format, args.output) else: print(format_outputs(outputs, args.format)) except ParseError as e: print(f"{e.loc}: {e}") return 1 except DataError as e: print(str(e)) return 1 return 0
def _parser_callback(args): if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( "Directory {0} is not a valid path!".format(args.instance_path)) storage = Storage.create(args.instance_path) if args.workers < 1: print("{0} is not a positive number!".format(args.workers)) return 1 delete_existing_state = False if storage.exists("instances"): if args.resume: if not args.force: print("The resume deploy option might have unexpected " "consequences on the already deployed blueprint.") question = prompt_yes_no_question() if not question: return 0 elif args.clean_state: if args.force: delete_existing_state = True else: print("The clean state deploy option might have unexpected " "consequences on the already deployed blueprint.") question = prompt_yes_no_question() if question: delete_existing_state = True else: return 0 else: print("The instance model already exists. Use --resume to " "continue or --clean-state to delete current deployment " "state and start over the deployment.") return 0 if args.template: service_template = args.template.name else: if storage.exists("root_file"): service_template = storage.read("root_file") else: print("CSAR or template root file does not exist. " "Maybe you have forgotten to initialize it.") return 1 try: if args.inputs: inputs = yaml.safe_load(args.inputs) else: inputs = None except Exception as e: print("Invalid inputs: {}".format(e)) return 1 try: deploy(service_template, inputs, storage, args.verbose, args.workers, delete_existing_state) except ParseError as e: print("{}: {}".format(e.loc, e)) return 1 except DataError as e: print(str(e)) return 1 return 0
def write_invocation(cls, inv: Invocation): storage = Storage.create(".opera-api") filename = "invocation-{}.json".format(inv.id) dump = json.dumps(inv.to_dict()) storage.write(dump, filename)
def _parser_callback(args): # pylint: disable=too-many-statements if args.instance_path and not path.isdir(args.instance_path): raise argparse.ArgumentTypeError( f"Directory {args.instance_path} is not a valid path!") if args.workers < 1: print(f"{args.workers} is not a positive number!") return 1 storage = Storage.create(args.instance_path) status = info(None, storage)["status"] delete_existing_state = False if storage.exists("instances"): if args.resume and status == "error": if not args.force: print( "The resume deploy option might have unexpected consequences on the already deployed blueprint." ) question = prompt_yes_no_question() if not question: return 0 elif args.clean_state: if args.force: delete_existing_state = True else: print("The clean state deploy option might have unexpected " "consequences on the already deployed blueprint.") question = prompt_yes_no_question() if question: delete_existing_state = True else: return 0 elif status == "initialized": print( "The project is initialized. You have to deploy it first to be able to run undeploy." ) return 0 elif status == "undeploying": print( "The project is currently undeploying. Please try again after the undeployment." ) return 0 elif status == "deployed": print("All instances have already been deployed.") return 0 elif status == "error": print( "The instance model already exists. Use --resume/-r to continue or --clean-state/-c to delete " "current deployment state and start over the deployment.") return 0 if args.template: csar_or_st_path = PurePath(args.template.name) else: if storage.exists("root_file"): csar_or_st_path = PurePath(storage.read("root_file")) else: print( "CSAR or template root file does not exist. Maybe you have forgotten to initialize it." ) return 1 try: if args.inputs: inputs = yaml.safe_load(args.inputs) else: inputs = None except yaml.YAMLError as e: print(f"Invalid inputs: {e}") return 1 try: if is_zipfile(csar_or_st_path): deploy_compressed_csar(csar_or_st_path, inputs, storage, args.verbose, args.workers, delete_existing_state) else: deploy_service_template(csar_or_st_path, inputs, storage, args.verbose, args.workers, delete_existing_state) except ParseError as e: print(f"{e.loc}: {e}") return 1 except DataError as e: print(str(e)) return 1 return 0
def prepare_template(path, yaml_text, template): name = PurePath("service.yaml") (path / name).write_text(yaml_text(template)) storage = Storage(path / Path(".opera")) return template, path, storage