diff --git a/openpype/modules/royalrender/lib.py b/openpype/modules/royalrender/lib.py index 9c4221d9cd2..3897e85824c 100644 --- a/openpype/modules/royalrender/lib.py +++ b/openpype/modules/royalrender/lib.py @@ -18,6 +18,7 @@ RRJob, RREnvList, get_rr_platform, + SubmitterParameter ) from openpype.pipeline import OpenPypePyblishPluginMixin from openpype.pipeline.publish import KnownPublishError @@ -110,7 +111,7 @@ def process(self, instance): if not self._rr_root: raise KnownPublishError( ("Missing RoyalRender root. " - "You need to configure RoyalRender module.")) + "Admin needs to configure RoyalRender module in Settings .")) self.rr_api = rrApi(self._rr_root) @@ -175,6 +176,19 @@ def get_job(self, instance, script_path, render_path, node_name): instance, render_path, start_frame, end_frame) instance.data["expectedFiles"].extend(expected_files) + submitter_parameters = [] + + anatomy_data = instance.context.data["anatomyData"] + environment = RREnvList({ + "AVALON_PROJECT": anatomy_data["project"]["name"], + "AVALON_ASSET": instance.context.data["asset"], + "AVALON_TASK": anatomy_data["task"]["name"], + "AVALON_APP_NAME": instance.context.data.get("appName"), + "AYON_RENDER_JOB": "1", + "AYON_BUNDLE_NAME": os.environ["AYON_BUNDLE_NAME"] + }) + + render_dir = render_dir.replace("\\", "/") job = RRJob( Software="", Renderer="", @@ -185,7 +199,7 @@ def get_job(self, instance, script_path, render_path, node_name): Version=0, SceneName=script_path, IsActive=True, - ImageDir=render_dir.replace("\\", "/"), + ImageDir=render_dir, ImageFilename=file_name, ImageExtension=file_ext, ImagePreNumberLetter="", @@ -197,7 +211,10 @@ def get_job(self, instance, script_path, render_path, node_name): CompanyProjectName=instance.context.data["projectName"], ImageWidth=instance.data["resolutionWidth"], ImageHeight=instance.data["resolutionHeight"], - CustomAttributes=custom_attributes + CustomAttributes=custom_attributes, + SubmitterParameters=submitter_parameters, + rrEnvList=environment.serialize(), + rrEnvFile=os.path.join(render_dir, "rrEnv.rrEenv"), ) return job @@ -307,68 +324,3 @@ def pad_file_name(self, path, first_frame): path = path.replace(first_frame, "#" * padding) return path - - def inject_environment(self, instance, job): - # type: (pyblish.api.Instance, RRJob) -> RRJob - """Inject environment variables for RR submission. - - This function mimics the behaviour of the Deadline - integration. It is just temporary solution until proper - runtime environment injection is implemented in RR. - - Args: - instance (pyblish.api.Instance): Publishing instance - job (RRJob): RRJob instance to be injected. - - Returns: - RRJob: Injected RRJob instance. - - Throws: - RuntimeError: If any of the required env vars is missing. - - """ - - temp_file_name = "{}_{}.json".format( - datetime.utcnow().strftime('%Y%m%d%H%M%S%f'), - str(uuid.uuid1()) - ) - - export_url = os.path.join(tempfile.gettempdir(), temp_file_name) - print(">>> Temporary path: {}".format(export_url)) - - args = [ - "--headless", - "extractenvironments", - export_url - ] - - anatomy_data = instance.context.data["anatomyData"] - - add_kwargs = { - "project": anatomy_data["project"]["name"], - "asset": instance.context.data["asset"], - "task": anatomy_data["task"]["name"], - "app": instance.context.data.get("appName"), - "envgroup": "farm" - } - - if os.getenv('IS_TEST'): - args.append("--automatic-tests") - - if not all(add_kwargs.values()): - raise RuntimeError(( - "Missing required env vars: AVALON_PROJECT, AVALON_ASSET," - " AVALON_TASK, AVALON_APP_NAME" - )) - - for key, value in add_kwargs.items(): - args.extend([f"--{key}", value]) - self.log.debug("Executing: {}".format(" ".join(args))) - run_openpype_process(*args, logger=self.log) - - self.log.debug("Loading file ...") - with open(export_url) as fp: - contents = json.load(fp) - - job.rrEnvList = RREnvList(contents).serialize() - return job diff --git a/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py index 775a2964fd4..36f3dcc99a6 100644 --- a/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_maya_royalrender_job.py @@ -38,6 +38,5 @@ def process(self, instance): job = self.get_job(instance, self.scene_path, first_file_path, layer_name) job = self.update_job_with_host_specific(instance, job) - job = self.inject_environment(instance, job) instance.data["rrJobs"].append(job) diff --git a/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py index 4f589e56f89..71daa6edf80 100644 --- a/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_nuke_royalrender_job.py @@ -25,7 +25,6 @@ def process(self, instance): jobs = self.create_jobs(instance) for job in jobs: job = self.update_job_with_host_specific(instance, job) - job = self.inject_environment(instance, job) instance.data["rrJobs"].append(job) diff --git a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index d4af1c2aeef..b5390bff1f4 100644 --- a/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/openpype/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -73,7 +73,7 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, "AVALON_APP_NAME", "OPENPYPE_USERNAME", "OPENPYPE_SG_USER", - "OPENPYPE_MONGO" + "AYON_BUNDLE_NAME" ] priority = 50 @@ -231,13 +231,13 @@ def get_job(self, instance, instances): ] job = RRJob( - Software="OpenPype", + Software="AYON", Renderer="Once", SeqStart=1, SeqEnd=1, SeqStep=1, SeqFileOffset=0, - Version=self._sanitize_version(os.environ.get("OPENPYPE_VERSION")), + Version=os.environ["AYON_BUNDLE_NAME"], SceneName=abs_metadata_path, # command line arguments CustomAddCmdFlags=" ".join(args), @@ -264,26 +264,3 @@ def get_job(self, instance, instances): job.WaitForPreIDs += jobs_pre_ids return job - - def _sanitize_version(self, version): - """Returns version in format MAJOR.MINORPATCH - - 3.15.7-nightly.2 >> 3.157 - """ - VERSION_REGEX = re.compile( - r"(?P0|[1-9]\d*)" - r"\.(?P0|[1-9]\d*)" - r"\.(?P0|[1-9]\d*)" - r"(?:-(?P[a-zA-Z\d\-.]*))?" - r"(?:\+(?P[a-zA-Z\d\-.]*))?" - ) - - valid_parts = VERSION_REGEX.findall(version) - if len(valid_parts) != 1: - # Return invalid version with filled 'origin' attribute - return version - - # Unpack found version - major, minor, patch, pre, post = valid_parts[0] - - return "{}.{}{}".format(major, minor, patch) diff --git a/openpype/modules/royalrender/plugins/publish/submit_jobs_to_royalrender.py b/openpype/modules/royalrender/plugins/publish/submit_jobs_to_royalrender.py index 8fc8604b839..0e809ee8ca1 100644 --- a/openpype/modules/royalrender/plugins/publish/submit_jobs_to_royalrender.py +++ b/openpype/modules/royalrender/plugins/publish/submit_jobs_to_royalrender.py @@ -99,7 +99,8 @@ def create_file(self, name, ext, contents=None): return temp.name def get_submission_parameters(self): - return [SubmitterParameter("RequiredMemory", "0")] + return [SubmitterParameter("RequiredMemory", "0"), + SubmitterParameter("PPAyoninjectenvvar", "1~1")] @staticmethod def _resolve_rr_path(context, rr_path_name): diff --git a/openpype/modules/royalrender/royal_render_module.py b/openpype/modules/royalrender/royal_render_module.py index 10d74d01d1a..f3ee040921b 100644 --- a/openpype/modules/royalrender/royal_render_module.py +++ b/openpype/modules/royalrender/royal_render_module.py @@ -3,6 +3,9 @@ import os import openpype.modules from openpype.modules import OpenPypeModule, IPluginPaths +from openpype import AYON_SERVER_ENABLED +from openpype.lib import Logger + class RoyalRenderModule(OpenPypeModule, IPluginPaths): @@ -31,6 +34,11 @@ def initialize(self, module_settings): self.enabled = rr_settings["enabled"] self.rr_paths = rr_settings.get("rr_paths") + # Ayon only + if not AYON_SERVER_ENABLED: + self.log.info("RoyalRender is not implemented for Openpype") + self.enabled = False + @staticmethod def get_plugin_paths(): # type: () -> dict diff --git a/openpype/modules/royalrender/rr_job.py b/openpype/modules/royalrender/rr_job.py index 62a82d45e85..10f09ed878f 100644 --- a/openpype/modules/royalrender/rr_job.py +++ b/openpype/modules/royalrender/rr_job.py @@ -173,6 +173,7 @@ class RRJob(object): # Environment # only used in RR 8.3 and newer rrEnvList = attr.ib(default=None, type=str) # type: str + rrEnvFile = attr.ib(default=None, type=str) # type: str class SubmitterParameter: diff --git a/openpype/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py b/openpype/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py deleted file mode 100644 index cdc37588cd0..00000000000 --- a/openpype/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -"""This is RR control plugin that runs on the job by user interaction. - -It asks user for context to publish, getting it from OpenPype. In order to -run it needs `OPENPYPE_ROOT` to be set to know where to execute OpenPype. - -""" -import rr # noqa -import rrGlobal # noqa -import subprocess -import os -import glob -import platform -import tempfile -import json - - -class OpenPypeContextSelector: - """Class to handle publishing context determination in RR.""" - - def __init__(self): - self.job = rr.getJob() - self.context = {} - - self.openpype_executable = "openpype_gui" - if platform.system().lower() == "windows": - self.openpype_executable = "{}.exe".format( - self.openpype_executable) - - op_path = os.environ.get("OPENPYPE_ROOT") - print("initializing ... {}".format(op_path)) - if not op_path: - print("Warning: OpenPype root is not found.") - - if platform.system().lower() == "windows": - print(" * trying to find OpenPype on local computer.") - op_path = os.path.join( - os.environ.get("PROGRAMFILES"), - "OpenPype", "openpype_console.exe" - ) - if not os.path.exists(op_path): - # try to find in user local context - op_path = os.path.join( - os.environ.get("LOCALAPPDATA"), - "Programs", - "OpenPype", "openpype_console.exe" - ) - if not os.path.exists(op_path): - raise Exception("Error: OpenPype was not found.") - - op_path = os.path.dirname(op_path) - print(" - found OpenPype installation {}".format(op_path)) - - self.openpype_root = op_path - - def _process_metadata_file(self): - """Find and process metadata file. - - Try to find metadata json file in job folder to get context from. - - Returns: - dict: Context from metadata json file. - - """ - image_dir = self.job.imageDir - metadata_files = glob.glob( - "{}{}*_metadata.json".format(image_dir, os.path.sep)) - if not metadata_files: - return {} - - raise NotImplementedError( - "Processing existing metadata not implemented yet.") - - def process_job(self): - """Process selected job. - - This should process selected job. If context can be determined - automatically, no UI will be show and publishing will directly - proceed. - """ - if not self.context and not self.show(): - return - - self.context["user"] = self.job.userName - self.run_publish() - - def show(self): - """Show UI for context selection. - - Because of RR UI limitations, this must be done using OpenPype - itself. - - """ - tf = tempfile.TemporaryFile(delete=False) - context_file = tf.name - op_args = [os.path.join(self.openpype_root, self.openpype_executable), - "contextselection", tf.name] - - tf.close() - print(">>> running {}".format(" ".join(op_args))) - - subprocess.call(op_args) - - with open(context_file, "r") as cf: - self.context = json.load(cf) - - os.unlink(context_file) - print(">>> context: {}".format(self.context)) - - if not self.context or \ - not self.context.get("project") or \ - not self.context.get("asset") or \ - not self.context.get("task"): - self._show_rr_warning("Context selection failed.") - return False - - # self.context["app_name"] = self.job.renderer.name - # there should be mapping between OpenPype and Royal Render - # app names and versions, but since app_name is not used - # currently down the line (but it is required by OP publish command - # right now). - # self.context["app_name"] = "maya/2022" - return True - - @staticmethod - def _show_rr_warning(text): - warning_dialog = rrGlobal.getGenericUI() - warning_dialog.addItem(rrGlobal.genUIType.label, "infoLabel", "") - warning_dialog.setText("infoLabel", text) - warning_dialog.addItem( - rrGlobal.genUIType.layoutH, "btnLayout", "") - warning_dialog.addItem( - rrGlobal.genUIType.closeButton, "Ok", "btnLayout") - warning_dialog.execute() - del warning_dialog - - def run_publish(self): - """Run publish process.""" - env = {"AVALON_PROJECT": str(self.context.get("project")), - "AVALON_ASSET": str(self.context.get("asset")), - "AVALON_TASK": str(self.context.get("task")), - # "AVALON_APP_NAME": str(self.context.get("app_name")) - } - - print(">>> setting environment:") - for k, v in env.items(): - print(" {}: {}".format(k, v)) - - publishing_paths = [os.path.join(self.job.imageDir, - os.path.dirname( - self.job.imageFileName))] - - # add additional channels - channel_idx = 0 - channel = self.job.channelFileName(channel_idx) - while channel: - channel_path = os.path.dirname( - os.path.join(self.job.imageDir, channel)) - if channel_path not in publishing_paths: - publishing_paths.append(channel_path) - channel_idx += 1 - channel = self.job.channelFileName(channel_idx) - - args = [os.path.join(self.openpype_root, self.openpype_executable), - 'publish', '-t', "rr_control", "--gui" - ] - - args += publishing_paths - - print(">>> running {}".format(" ".join(args))) - orig = os.environ.copy() - orig.update(env) - try: - subprocess.call(args, env=orig) - except subprocess.CalledProcessError as e: - self._show_rr_warning(" Publish failed [ {} ]".format( - e.returncode - )) - - -print("running selector") -selector = OpenPypeContextSelector() - -# try to set context from environment -selector.context["project"] = os.getenv("AVALON_PROJECT") -selector.context["asset"] = os.getenv("AVALON_ASSET") -selector.context["task"] = os.getenv("AVALON_TASK") -# selector.context["app_name"] = os.getenv("AVALON_APP_NAME") - -# if anything inside is None, scratch the whole thing and -# ask user for context. -for _, v in selector.context.items(): - if not v: - selector.context = {} - break - -selector.process_job() diff --git a/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype___global.inc b/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype___global.inc deleted file mode 100644 index ba38337340d..00000000000 --- a/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype___global.inc +++ /dev/null @@ -1,2 +0,0 @@ -IconApp= E01__OpenPype.png -Name= OpenPype diff --git a/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype.png b/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon.png similarity index 100% rename from openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype.png rename to openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon.png diff --git a/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype__PublishJob.cfg b/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon__PublishJob.cfg similarity index 94% rename from openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype__PublishJob.cfg rename to openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon__PublishJob.cfg index 864eeaf15aa..9604d3c468c 100644 --- a/openpype/modules/royalrender/rr_root/render_apps/_config/E01__OpenPype__PublishJob.cfg +++ b/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon__PublishJob.cfg @@ -1,5 +1,5 @@ -IconApp= E01__OpenPype.png -Name= OpenPype +IconApp= E05__Ayon.png +Name= Ayon rendererName= Once Version= 1 Version_Minor= 0 @@ -59,7 +59,7 @@ AllowLocalRenderOut= 0~0 ################################## Client Settings ################################## -IconApp=E01__OpenPype.png +IconApp=E05__Ayon.png licenseFailLine= diff --git a/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon___global.inc b/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon___global.inc new file mode 100644 index 00000000000..1581eb26b11 --- /dev/null +++ b/openpype/modules/royalrender/rr_root/render_apps/_config/E05__Ayon___global.inc @@ -0,0 +1,2 @@ +IconApp= E05__Ayon.png +Name= Ayon diff --git a/openpype/modules/royalrender/rr_root/render_apps/_install_paths/Ayon.cfg b/openpype/modules/royalrender/rr_root/render_apps/_install_paths/Ayon.cfg new file mode 100644 index 00000000000..534eb868990 --- /dev/null +++ b/openpype/modules/royalrender/rr_root/render_apps/_install_paths/Ayon.cfg @@ -0,0 +1,12 @@ +[Windows] +Executable= ayon_console.exe +Path= OS; \Ayon\*\ayon_console.exe +Path= 32; \Ayon\*\ayon_console.exe + +[Linux] +Executable= ayon_console +Path= OS; /opt/ayon/*/ayon_console + +[Mac] +Executable= ayon_console +Path= OS; /Applications/Ayon*/Content/MacOS/ayon_console diff --git a/openpype/modules/royalrender/rr_root/render_apps/_install_paths/OpenPype.cfg b/openpype/modules/royalrender/rr_root/render_apps/_install_paths/OpenPype.cfg deleted file mode 100644 index 07f7547d297..00000000000 --- a/openpype/modules/royalrender/rr_root/render_apps/_install_paths/OpenPype.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[Windows] -Executable= openpype_console.exe -Path= OS; \OpenPype\*\openpype_console.exe -Path= 32; \OpenPype\*\openpype_console.exe - -[Linux] -Executable= openpype_console -Path= OS; /opt/openpype/*/openpype_console - -[Mac] -Executable= openpype_console -Path= OS; /Applications/OpenPype*/Content/MacOS/openpype_console diff --git a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/Ayon_ayon_inject_envvar.cfg b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/Ayon_ayon_inject_envvar.cfg new file mode 100644 index 00000000000..d54e83b237d --- /dev/null +++ b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/Ayon_ayon_inject_envvar.cfg @@ -0,0 +1,42 @@ +# config file format version 7.0 +# +# Author: Royal Render, Holger Schoenberger, Binary Alchemy +# +# Last change: v8.2.24 +# +# +# Deletes this job from the rrServer queue +# +# AuthStr is required in case anonymous does not have the right to delete jobs. +# Or if you have enabled "Authorization is required for all connections" +# AuthStr will not work via a router/remote connection +# +# +################################## Identify script ################################## +Name= Ayon inject env var + +PrePostType=Pre + +# Optional flags: The following lines are disabled and set to the default value +# AllowedForExecuteOnceJobs= false +# AllowedForSingleOutput = true +PrePostChecked= true +# ExecutePerChannel = false +# PrePostShowParamA= false +# PrePostShowParamB= false +# PrePostParamA= 100 +# PrePostParamB= 100 + + +################################## [Windows] [Linux] [OSX] ################################## + +CommandLine= + + + +CommandLine= rrPythonconsole" > "render_apps/_prepost_scripts/ayon_inject_envvar.py" -jid + + + +CommandLine= + diff --git a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/OpenPypeEnvironment.cfg b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/OpenPypeEnvironment.cfg deleted file mode 100644 index 70f0bc2e244..00000000000 --- a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/OpenPypeEnvironment.cfg +++ /dev/null @@ -1,11 +0,0 @@ -PrePostType= pre -CommandLine= - -CommandLine= rrPythonconsole" > "render_apps/_prepost_scripts/PreOpenPypeInjectEnvironments.py" - -CommandLine= - - -CommandLine= "" -CommandLine= - diff --git a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/PreOpenPypeInjectEnvironments.py b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/PreOpenPypeInjectEnvironments.py deleted file mode 100644 index 891de9594c5..00000000000 --- a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/PreOpenPypeInjectEnvironments.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -import os - -os.environ["OPENYPYPE_TESTVAR"] = "OpenPype was here" diff --git a/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/ayon_inject_envvar.py b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/ayon_inject_envvar.py new file mode 100644 index 00000000000..9316410bc16 --- /dev/null +++ b/openpype/modules/royalrender/rr_root/render_apps/_prepost_scripts/ayon_inject_envvar.py @@ -0,0 +1,225 @@ +import os +import sys +import json +import glob +from datetime import datetime +import tempfile +import subprocess +import uuid +import argparse + +modPath = rrGlobal.rrModPyrrPath() +sys.path.append(modPath) +import libpyRR39 as rr + +logs = [] + + +class InjectEnvironment: + """Creates rrEnv file. + + RR evnList has limitation on 2000 characters, which might not be enough. + This script should be triggered by render jobs that were published from + Ayon, it uses .json metadata to parse context and required Ayon launch + environments to generate environment variable file for particular context. + + This file is converted into rrEnv file. + + Render job already points to non-existent location which got filled only + by this process. (Couldn't figure way how to attach new file to existing + job.) + + Expected set environments on RR worker: + - AYON_SERVER_URL + - AYON_API_KEY - API key to Ayon server, most likely from service account + - AYON_EXECUTABLE_PATH - locally accessible path for `ayon_console` + (could be removed if it would be possible to have it in renderApps config + and to be accessible from there as there it is required for publish jobs). + - AYON_FILTER_ENVIRONMENTS - potential black list of unwanted environment + variables (separated by ';') - will be filtered out from created .rrEnv. + + Ayon submission job must be adding this line to .xml submission file: + PPAyoninjectenvvar=1~1 + + Scripts logs into folder with metadata json - could be removed if there + is a way how to log into RR output. + + """ + + def __init__(self): + self.meta_dir = None + + def inject(self): + logs.append("InjectEnvironment starting") + meta_dir = self._get_metadata_dir() + self.meta_dir = meta_dir + + envs = self._get_job_environments() + if not envs.get("AYON_RENDER_JOB"): + logs.append("Not a ayon render job, skipping.") + return + + if not self._is_required_environment(): + return + + context = self._get_context() + logs.append("context {}".format(context)) + + executable = self._get_executable() + logs.append("executable {}".format(executable)) + + extracted_env = self._extract_environments(executable, context) + + rrEnv_path = self._create_rrEnv(meta_dir, extracted_env) + + logs.append(f"InjectEnvironment ending, rrEnv file {rrEnv_path}") + + def _get_metadata_dir(self): + """Get folder where metadata.json and renders should be produced.""" + job = self._get_job() + image_dir = job.imageDir + logs.append(f"_get_metadata_dir::{image_dir}") + + return image_dir + + def _is_required_environment(self): + if (not os.environ.get("AYON_API_KEY") or + not os.path.exists(os.environ.get("AYON_EXECUTABLE_PATH", ""))): + msg = ("AYON_API_KEY and AYON_EXECUTABLE_PATHenv var must be set " + "for Ayon jobs!") + logs.append(msg) + return False + return True + + def _get_context(self): + envs = self._get_job_environments() + + return {"project": envs["AVALON_PROJECT"], + "asset": envs["AVALON_ASSET"], + "task": envs["AVALON_TASK"], + "app": envs["AVALON_APP_NAME"], + "envgroup": "farm"} + + def _get_job(self): + logs.append("get_jobs") + parser = argparse.ArgumentParser() + parser.add_argument("-jid") + parser.add_argument("-authStr") + args = parser.parse_args() + + # print("Set up server and login info") + tcp = rr._rrTCP("") + tcp.setServer(rrGlobal.rrServer(), 7773) + # tcp.setLogin(args.authStr, "") + tcp.setLogin("", "") + logs.append("jid:{}".format(int(args.jid))) + if not tcp.jobList_GetInfo(int(args.jid)): + print("Error jobList_GetInfo: " + tcp.errorMessage()) + sys.exit() + + return tcp.jobs.getJobSend(int(args.jid)) + + def _get_job_environments(self): + """Gets environments set on job. + + It seems that it is not possible to query "rrEnvList" on job directly, + it must be parsed from .json document. + """ + job = self._get_job() + env_list = job.customData_Str('rrEnvList') + envs = {} + for env in env_list.split("~~~"): + key, value = env.split("=") + envs[key] = value + return envs + + def _get_executable(self): + # rr_python_utils.cache.get_rr_bin_folder() # TODO maybe useful + return os.environ["AYON_EXECUTABLE_PATH"] + + def _get_launch_environments(self): + """ Enhances environemnt with required for Ayon to be launched.""" + job_envs = self._get_job_environments() + ayon_environment = { + "AYON_SERVER_URL": os.environ["AYON_SERVER_URL"], + "AYON_API_KEY": os.environ["AYON_API_KEY"], + "AYON_BUNDLE_NAME": job_envs["AYON_BUNDLE_NAME"], + } + logs.append("Ayon launch environments:: {}".format(ayon_environment)) + environment = os.environ.copy() + environment.update(ayon_environment) + return environment + + def _get_export_url(self): + """Returns unique path with extracted env variables from Ayon.""" + temp_file_name = "{}_{}.json".format( + datetime.utcnow().strftime('%Y%m%d%H%M%S%f'), + str(uuid.uuid1()) + ) + export_url = os.path.join(tempfile.gettempdir(), temp_file_name) + return export_url + + def _extract_environments(self, executable, context): + # tempfile.TemporaryFile cannot be used because of locking + export_url = self._get_export_url() + + args = [ + executable, + "--headless", + "extractenvironments", + export_url + ] + + if all(context.values()): + for key, value in context.items(): + args.extend(["--{}".format(key), value]) + + environments = self._get_launch_environments() + + logs.append("Running:: {}".format(args)) + proc = subprocess.Popen(args, env=environments, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, error = proc.communicate() + + if not os.path.exists(export_url): + logs.append("output::{}".format(output)) + logs.append("error::{}".format(error)) + raise RuntimeError("Extract failed with {}".format(error)) + + with open(export_url) as json_file: + return json.load(json_file) + + def _create_rrEnv(self, meta_dir, extracted_env): + """Create rrEnv.rrEnv file in metadata folder that render job points""" + filter_out = os.environ.get("AYON_FILTER_ENVIRONMENTS") + filter_envs = set() + if filter_out: + filter_envs = set(filter_out.split(";")) + + lines = [] + for key, value in extracted_env.items(): + if key in filter_envs: + continue + + line = f"{key} = {value}" + lines.append(line) + + rrenv_path = os.path.join(meta_dir, "rrEnv.rrEnv") + with open(rrenv_path, "w") as fp: + fp.writelines(s + '\n' for s in lines) + + return rrenv_path + + +if __name__ == "__main__": + try: + injector = InjectEnvironment() + injector.inject() + except Exception as exp: + logs.append(f"Error happened::{str(exp)}") + + tmpdir = injector.meta_dir + log_path = os.path.join(tmpdir, "log.txt") + with open(log_path, "a") as fp: + fp.writelines(s.replace("\\r\\n", "\n") + '\n' for s in logs)