Repository: aws-deadline/deadline-cloud-for-nuke
Pull request #210: Fix individual installer build process
Author: login: MarinMaksutaj
Created: 2025-04-10T18:48:35Z | Merged: 2025-04-11T21:31:16Z

### What was the problem/requirement? (What/Why)

Some issues with individual installer changes caused the DCC integration teams to perform extra changes, this PR handles these fixes for Nuke.

### What was the solution? (How)

Followed the guidelines from the integration team to make these changes for Nuke.

### What is the impact of this change?

Successful individual installer build for different OS environments.

### How was this change tested?

tested locally to generate the executables. 

### Was this change documented?

-

### Is this a breaking change?
-



Diff:
diff --git a/.github/workflows/build_installers.yml b/.github/workflows/build_installers.yml
index 951366b8..08b244a7 100644
--- a/.github/workflows/build_installers.yml
+++ b/.github/workflows/build_installers.yml
@@ -13,7 +13,7 @@ on:
         - MacOS
       ref_type:
         required: true
-        default: tags
+        default: heads
         type: choice
         options:
         - tags
diff --git a/.github/workflows/release_publish.yml b/.github/workflows/release_publish.yml
index b9563779..2256ab44 100644
--- a/.github/workflows/release_publish.yml
+++ b/.github/workflows/release_publish.yml
@@ -17,7 +17,9 @@ jobs:
     permissions:
       id-token: write
       contents: write
-    uses: aws-deadline/.github/.github/workflows/reusable_publish.yml@mainline
+    uses: aws-deadline/.github/.github/workflows/reusable_publish_v2.yml@mainline
+    with:
+      installer_oses: "['Linux', 'Windows', 'MacOS']"
     secrets: inherit
   # PyPI does not support reusable workflows yet
   # # See https://github.com/pypi/warehouse/issues/11096
@@ -31,7 +33,7 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
         with:
-          ref: release
+          ref: ${{ needs.Publish.outputs.tag }}
           fetch-depth: 0
       - name: Set up Python
         uses: actions/setup-python@v5
diff --git a/depsBundle.sh b/depsBundle.sh
index 7b861ce8..7865c6a8 100755
--- a/depsBundle.sh
+++ b/depsBundle.sh
@@ -7,12 +7,4 @@
 
 set -xeuo pipefail
 
-python depsBundle.py
-
-rm -f dependency_bundle/deadline_cloud_for_nuke_submitter-deps-windows.zip
-rm -f dependency_bundle/deadline_cloud_for_nuke_submitter-deps-linux.zip
-rm -f dependency_bundle/deadline_cloud_for_nuke_submitter-deps-macos.zip
-
-cp dependency_bundle/deadline_cloud_for_nuke_submitter-deps.zip dependency_bundle/deadline_cloud_for_nuke_submitter-deps-windows.zip
-cp dependency_bundle/deadline_cloud_for_nuke_submitter-deps.zip dependency_bundle/deadline_cloud_for_nuke_submitter-deps-linux.zip
-cp dependency_bundle/deadline_cloud_for_nuke_submitter-deps.zip dependency_bundle/deadline_cloud_for_nuke_submitter-deps-macos.zip
+python scripts/depsBundle.py
\ No newline at end of file
diff --git a/install_builder/deadline-cloud-for-nuke.xml b/install_builder/deadline-cloud-for-nuke.xml
index b9889efa..edf9e091 100644
--- a/install_builder/deadline-cloud-for-nuke.xml
+++ b/install_builder/deadline-cloud-for-nuke.xml
@@ -50,30 +50,6 @@
                 <setInstallerVariable name="nuke_installdir" value="${installdir}/Submitters/Nuke" />
             </elseActionList>
         </if>
-        <if>
-             <conditionRuleList>
-                 <platformTest type="windows"/>
-             </conditionRuleList>
-             <actionList>
-                 <setInstallerVariable name="nuke_deps_platform" value="windows"/>
-             </actionList>
-         </if>
-         <if>
-             <conditionRuleList>
-                 <platformTest type="linux"/>
-             </conditionRuleList>
-             <actionList>
-                 <setInstallerVariable name="nuke_deps_platform" value="linux"/>
-             </actionList>
-         </if>
-         <if>
-             <conditionRuleList>
-                 <platformTest type="osx"/>
-             </conditionRuleList>
-             <actionList>
-                 <setInstallerVariable name="nuke_deps_platform" value="macos"/>
-             </actionList>
-         </if>
 	</readyToInstallActionList>
 	<parameterList>
 		<stringParameter name="deadline_cloud_for_nuke_summary" ask="0" cliOptionShow="0">
@@ -93,7 +69,7 @@
 		</fnAddPathEnvironmentVariable>
         <unzip>
             <destinationDirectory>${nuke_installdir}</destinationDirectory>
-            <zipFile>${installdir}/tmp/nuke_deps/dependency_bundle/deadline_cloud_for_nuke_submitter-deps-${nuke_deps_platform}.zip</zipFile>
+            <zipFile>${installdir}/tmp/nuke_deps/dependency_bundle/deadline_cloud_for_nuke_submitter-deps.zip</zipFile>
         </unzip>
         <deleteFile>
             <path>${installdir}/tmp/nuke_deps</path>
diff --git a/installer/DeadlineCloudForNuke.xml b/installer/DeadlineCloudForNuke.xml
index e5e64b00..f9463be7 100644
--- a/installer/DeadlineCloudForNuke.xml
+++ b/installer/DeadlineCloudForNuke.xml
@@ -8,6 +8,8 @@
     <enableTimestamp>1</enableTimestamp>
     <allowComponentSelection>1</allowComponentSelection>
 
+    <osxApplicationBundleIdentifier>com.amazonaws.deadline.clientinstaller</osxApplicationBundleIdentifier>
+
     <!-- Images and icons -->
     <logoImage>logoImage.png</logoImage>
     <leftImage>leftImage.png</leftImage>
@@ -214,6 +216,19 @@
     </preInstallationActionList>
     <postInstallationActionList>
         <setInstallerVariable name="installscope" value="${installscope}" persist="1"/>
+        <writeFile>
+            <ruleList>
+                <compareTextLength text="${_fnAddEnvironmentVariable_list}" logic="greater" length="0"/>
+            </ruleList>
+            <progressText>Writing environment variables to /etc/profile.d/deadline-cloud-for-nuke.sh</progressText>
+            <path>/etc/profile.d/deadline-cloud-for-nuke.sh</path>
+            <text>### THIS FILE IS GENERATED BY THE AWS DEADLINE CLOUD FOR NUKE SUBMITTER INSTALLER ###
+### ANY MANUAL CHANGES TO THIS FILE WILL BE OVERWRITTEN BY THE AWS DEADLINE CLOUD FOR NUKE SUBMITTER INSTALLER ###
+
+# Deadline submitter initialization script
+${_fnAddEnvironmentVariable_list}
+            </text>
+        </writeFile>
         <writeFile>
             <progressText>Writing version file</progressText>
             <path>${installdir}/installer_version.txt</path>
diff --git a/scripts/build_installer.py b/scripts/build_installer.py
index 98c2be38..e1dc1b7b 100644
--- a/scripts/build_installer.py
+++ b/scripts/build_installer.py
@@ -2,8 +2,6 @@
 """Script to create platform-specific Deadline Client installers using InstallBuilder."""
 
 import os
-import platform
-import subprocess
 import sys
 import shutil
 import tempfile
@@ -12,7 +10,8 @@
 from pathlib import Path
 
 from common import EvaluationBuildError, run
-from find_installbuilder import InstallBuilderSelection
+from find_installbuilder import InstallBuilderSelection, get_builder_exe_name
+from depsBundle import build_deps_bundle
 
 # This is derived from <installerFilename> in installer/DeadlineCloudClient.xml
 # See "Supported Platforms" table in https://releases.installbuilder.com/installbuilder/docs/installbuilder-userguide.html
@@ -50,17 +49,12 @@ def setup_install_builder(
 
     install_builder_path = selection.resolve_install_builder_installation(workdir)
 
-    if platform.system() == "Windows":
-        binary_name = "builder.exe"
-    else:
-        binary_name = "builder"
-
     if (
         not install_builder_path.is_dir()
-        or not (install_builder_path / "bin" / binary_name).is_file()
+        or not (install_builder_path / "bin" / get_builder_exe_name()).is_file()
     ):
         raise FileNotFoundError(
-            f"InstallBuilder path '{install_builder_path}' must be a directory containing 'bin/{binary_name}'."
+            f"InstallBuilder path '{install_builder_path}' must be a directory containing 'bin/{get_builder_exe_name()}'."
         )
 
     if license_file_path is not None:
@@ -97,21 +91,7 @@ def build_installer(
         installbuilder_platform = "windows-x64"
     else:
         raise ValueError(f"Unknown platform '{installer_platform}'")
-
-    try:
-        if platform.system() == "Windows":
-            # `shell=True` is necessary here to run on Windows.
-            # Please see the security considerations of this flag if editing the script or its invocation:
-            # https://docs.python.org/3/library/subprocess.html#security-considerations
-            deps_bundle_output = subprocess.run(
-                "depsBundle.sh", check=True, shell=True, capture_output=True
-            )
-        else:
-            deps_bundle_output = subprocess.run("./depsBundle.sh", check=True, capture_output=True)
-        print(deps_bundle_output.stdout.decode("utf-8"))
-    except subprocess.CalledProcessError as e:
-        print(f"Error when bundling dependencies: {e.stdout.decode('utf-8')}")
-        raise
+    build_deps_bundle()
 
     install_builder_cli = install_builder_location / "bin" / "builder"
     out_dir = workdir / "out"
@@ -192,7 +172,7 @@ def main(
                 f"Found:\n\t{os.linesep.join([str(i) for i in installer_dir.iterdir()])}"
             )
 
-        output_path = installer_filename
+        output_path = Path(installer_filename)
         if output_dir:
             output_dir.mkdir(exist_ok=True)
             output_path = output_dir / output_path
diff --git a/depsBundle.py b/scripts/depsBundle.py
similarity index 82%
rename from depsBundle.py
rename to scripts/depsBundle.py
index 892003c6..31cf87b0 100644
--- a/depsBundle.py
+++ b/scripts/depsBundle.py
@@ -9,8 +9,7 @@
 from tempfile import TemporaryDirectory
 from typing import Any
 
-SUPPORTED_PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11"]
-SUPPORTED_PLATFORMS = ["win_amd64", "manylinux2014_x86_64", "macosx_10_9_x86_64"]
+SUPPORTED_PYTHON_VERSIONS = ["3.9", "3.10", "3.11"]
 NATIVE_DEPENDENCIES = ["xxhash"]
 
 
@@ -20,7 +19,7 @@ def _get_project_dict() -> dict[str, Any]:
             toml_install_pip_args = ["pip", "install", "--target", toml_env, "toml"]
             subprocess.run(toml_install_pip_args, check=True)
             sys.path.insert(0, toml_env)
-            import toml
+            import toml  # type: ignore
         mode = "r"
     else:
         import tomllib as toml
@@ -79,25 +78,20 @@ def _download_native_dependencies(working_directory: Path, base_env: Path) -> li
     ]
     native_dependency_paths = []
     for version in SUPPORTED_PYTHON_VERSIONS:
-        for platform in SUPPORTED_PLATFORMS:
-            native_dependency_path = (
-                working_directory / "native" / f"{version.replace('.', '_')}_{platform}"
-            )
-            native_dependency_paths.append(native_dependency_path)
-            native_dependency_path.mkdir(parents=True)
-            native_dependency_pip_args = [
-                "pip",
-                "install",
-                "--target",
-                str(native_dependency_path),
-                "--platform",
-                platform,
-                "--python-version",
-                version,
-                "--only-binary=:all:",
-                *versioned_native_dependencies,
-            ]
-            subprocess.run(native_dependency_pip_args, check=True)
+        native_dependency_path = working_directory / "native" / f"{version.replace('.', '_')}"
+        native_dependency_paths.append(native_dependency_path)
+        native_dependency_path.mkdir(parents=True)
+        native_dependency_pip_args = [
+            "pip",
+            "install",
+            "--target",
+            str(native_dependency_path),
+            "--python-version",
+            version,
+            "--only-binary=:all:",
+            *versioned_native_dependencies,
+        ]
+        subprocess.run(native_dependency_pip_args, check=True)
     return native_dependency_paths
 
 
@@ -127,13 +121,14 @@ def _zip_bundle(base_env: Path, zip_path: Path) -> None:
     shutil.make_archive(str(zip_path.with_suffix("")), "zip", str(base_env))
 
 
-def _copy_zip_to_destination(zip_path: Path) -> None:
+def _copy_zip_to_destination(zip_path: Path) -> Path:
     dependency_bundle_dir = Path.cwd() / "dependency_bundle"
     dependency_bundle_dir.mkdir(exist_ok=True)
     zip_desntination = dependency_bundle_dir / zip_path.name
     if zip_desntination.exists():
         zip_desntination.unlink()
     shutil.copy(str(zip_path), str(zip_desntination))
+    return zip_desntination
 
 
 def build_deps_bundle() -> None:
diff --git a/scripts/find_installbuilder.py b/scripts/find_installbuilder.py
index 1a0617fb..ad14c5e5 100644
--- a/scripts/find_installbuilder.py
+++ b/scripts/find_installbuilder.py
@@ -15,6 +15,7 @@
 _INSTALL_BUILDER_ARCHIVE_FILENAME = {
     "Windows": "VMware-InstallBuilder-Professional-windows.tar.gz",
     "Linux": "VMware-InstallBuilder-Professional-linux.tar.gz",
+    "Darwin": "VMware-InstallBuilder-Professional-macos.tar.gz",
 }
 
 
@@ -150,6 +151,12 @@ def _sort_key(install: "_InstallBuilderInstallation") -> tuple[int, int, int, in
         )
 
 
+def get_builder_exe_name() -> str:
+    if platform.system() == "Windows":
+        return "builder.exe"
+    return "builder"
+
+
 def _get_default_installbuilder_location() -> Path:
     """
     Returns the default location where InstallBuilder Professional will be installed depending on the platform.
@@ -161,7 +168,7 @@ def _get_default_installbuilder_location() -> Path:
     for install_dir in config.parent_path.iterdir():
         if install_dir.is_dir():
             match = config.get_install_directory_regex().match(install_dir.name)
-            if match and (install_dir / "bin" / "builder").is_file():
+            if match and (install_dir / "bin" / get_builder_exe_name()).is_file():
                 if platform.system() == "Linux":
                     version_offset = 0
                 else:
diff --git a/test/installer/test_installer.py b/test/installer/test_installer.py
index 429b1182..0f9c2a50 100644
--- a/test/installer/test_installer.py
+++ b/test/installer/test_installer.py
@@ -171,7 +171,7 @@ def test_default_location(installer_path: Path):
 
     assert (
         location is not None
-    ), f"Could not find default install location in help output:\n{help_result.stdout}"
+    ), f"Could not find default install location in help output:\n{help_output}"
     if platform.system() != "Windows":
         assert location.group(1) == default_install_location.as_posix()
     else:
@@ -179,7 +179,7 @@ def test_default_location(installer_path: Path):
 
 
 @pytest.mark.skipif(
-    os.getenv("CODEBUILD_SRC_DIR") is None,
+    os.getenv("CODEBUILD_SRC_DIR") is None or os.getenv("IS_DEV_BUILD", "false").lower() == "true",
     reason="Only installers built with a license will not be evaluation mode",
 )
 @pytest.mark.skipif(
@@ -258,10 +258,12 @@ def _has_user_group_other_execute(self, mode: int) -> bool:
         return bool(mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH))
 
     def _validate_posix_permissions(self, installation_path: Path):
+        import pwd
+
         # assists mypy type checking to ignore this on Windows
         assert sys.platform != "win32"
         # GIVEN
-        current_user = getpass.getuser()
+        current_user = pwd.getpwuid(os.getuid())[0]  # type: ignore
 
         # WHEN
         bad_perms: defaultdict[Path, list[str]] = defaultdict(list)
@@ -299,6 +301,23 @@ def test_system_permissions(self, system_installation):
         # GIVEN / WHEN / THEN
         self._verify_windows_least_privilege(system_installation)
 
+    def _running_in_container(self) -> bool:
+        """
+        Check to see if the cexecsvc service exists and is running
+        to determine if we're running on a container.
+        """
+        # assists mypy type checking to ignore this on non-Windows
+        assert sys.platform == "win32"
+        import win32service
+        import win32serviceutil
+
+        try:
+            service_status = win32serviceutil.QueryServiceStatus("cexecsvc")
+            return service_status[1] == win32service.SERVICE_RUNNING
+        except win32service.error:
+            # Service doesn't exist, not on a container
+            return False
+
     def _verify_windows_least_privilege(self, installation_path: Path):
         # assists mypy type checking to ignore this on non-Windows
         assert sys.platform == "win32"
@@ -309,7 +328,13 @@ def _verify_windows_least_privilege(self, installation_path: Path):
 
         # GIVEN
         windows_user = getpass.getuser()
-        builtin_admin_group_sid, _, _ = win32security.LookupAccountName(None, "Administrators")
+        if self._running_in_container():
+            # The admin group is different when running
+            # in a container.
+            admin_group = "ContainerAdministrator"
+        else:
+            admin_group = "Administrators"
+        builtin_admin_group_sid, _, _ = win32security.LookupAccountName(None, admin_group)
         user_sid, _, _ = win32security.LookupAccountName(None, windows_user)
 
         # WHEN
@@ -325,7 +350,7 @@ def _verify_windows_least_privilege(self, installation_path: Path):
             if _is_admin():
                 if builtin_admin_group_sid != owner_sid:
                     bad_perms[path].append(
-                        f"Expected to be owned by 'Administrators' but got '{win32security.LookupAccountSid('', owner_sid)}'"
+                        f"Expected to be owned by '{admin_group}' but got '{win32security.LookupAccountSid('', owner_sid)}'"
                     )
             elif user_sid != owner_sid:
                 bad_perms[path].append(
@@ -424,7 +449,7 @@ def test_uninstall(self, per_test_system_installation: Path, uninstaller_path: P
 
 
 @pytest.mark.skipif(
-    os.getenv("CODEBUILD_SRC_DIR") is None,
+    os.getenv("CODEBUILD_SRC_DIR") is None or os.getenv("IS_DEV_BUILD", "false").lower() == "true",
     reason="Only installers built internally will be signed",
 )
 class TestVerifySigning:
@@ -449,7 +474,12 @@ def test_windows_signing(self, installer_path):
             Number of errors: 1
         """
         # GIVEN
-        signtool = next(glob.iglob("C:/Program Files*/Windows Kits/*/bin/*/x64/signtool.exe"), None)
+        # Check PATH, then SDK installation location
+        signtool = shutil.which("signtool")
+        if not signtool:
+            signtool = next(
+                glob.iglob("C:/Program Files*/Windows Kits/*/bin/*/x64/signtool.exe"), None
+            )
         assert signtool, "signtool not found in expected location"
 
         # WHEN


Reviews:
user: login: viknith
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:39:32Z
user: login: viknith
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:40:48Z
user: login: viknith
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:41:17Z
user: login: joel-wong-aws
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T20:44:21Z
user: login: joel-wong-aws
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T20:45:27Z
user: login: MarinMaksutaj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T20:54:19Z
user: login: joel-wong-aws
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T21:11:24Z
user: login: viknith
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T21:28:12Z

Review comments:
diff_hunk: @@ -8,6 +8,8 @@
     <enableTimestamp>1</enableTimestamp>
     <allowComponentSelection>1</allowComponentSelection>
 
+    <osxApplicationBundleIdentifier>com.amazonaws.deadline.clientinstaller</osxApplicationBundleIdentifier>
path: installer/DeadlineCloudForNuke.xml
user: login: viknith
body: We don't need the bundle identifier right now, but someone please correct me if I'm wrong if it's okay to keep it anyways
created_at: 2025-04-11T16:39:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,13 +121,14 @@ def _zip_bundle(base_env: Path, zip_path: Path) -> None:
     shutil.make_archive(str(zip_path.with_suffix("")), "zip", str(base_env))
 
 
-def _copy_zip_to_destination(zip_path: Path) -> None:
+def _copy_zip_to_destination(zip_path: Path) -> Path:
     dependency_bundle_dir = Path.cwd() / "dependency_bundle"
     dependency_bundle_dir.mkdir(exist_ok=True)
     zip_desntination = dependency_bundle_dir / zip_path.name
     if zip_desntination.exists():
         zip_desntination.unlink()
path: scripts/depsBundle.py
user: login: viknith
body: Nit: zip_destination mis-spelling, can fix later
created_at: 2025-04-11T16:40:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@
 import glob
 import os
 import platform
+import pwd
path: test/installer/test_installer.py
user: login: joel-wong-aws
body: Will this fail if run locally on Windows? Should we move it just above `pwd.getpwuid(os.getuid())[0] # type: ignore`, like [here](https://github.com/aws-deadline/deadline-cloud-for-keyshot/pull/197/files)
created_at: 2025-04-11T20:43:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,7 +180,7 @@ def test_default_location(installer_path: Path):
 
path: test/installer/test_installer.py
user: login: joel-wong-aws
body: Can we replace `help_result.stdout` ib kube 175 with `help_output` as well? Similar to https://github.com/aws-deadline/deadline-cloud-for-keyshot/pull/197/files
created_at: 2025-04-11T20:45:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@
 import glob
 import os
 import platform
+import pwd
path: test/installer/test_installer.py
user: login: MarinMaksutaj
body: Thank you for the catch, will change it really quick.
created_at: 2025-04-11T20:54:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-11T21:05:21Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aws-deadline_deadline-cloud-for-nuke&pullRequest=210)


author_association: NONE