From 36e9a6814fa012a56b1e248be414c29b77e5c956 Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Thu, 19 Mar 2026 03:19:13 +0000 Subject: [PATCH 1/6] add identity login support for paibuild.py --- build/core/build_center.py | 3 ++- build/core/build_utility.py | 28 +++++++++++++++++++++++++--- build/model/config_model.py | 4 ++++ build/pai_build.py | 21 +++++++++++++++++---- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/build/core/build_center.py b/build/core/build_center.py index 91814e29..74d68bc2 100644 --- a/build/core/build_center.py +++ b/build/core/build_center.py @@ -45,7 +45,8 @@ def __init__(self, build_config, process_list, type, arg_config=None): docker_registry = self.build_config['dockerRegistryInfo']['dockerRegistryDomain'], docker_namespace = self.build_config['dockerRegistryInfo']['dockerNameSpace'], docker_username = self.build_config['dockerRegistryInfo']['dockerUserName'], - docker_password = self.build_config['dockerRegistryInfo']['dockerPassword'] + docker_password = self.build_config['dockerRegistryInfo']['dockerPassword'], + managed_identity_id = self.build_config['dockerRegistryInfo']['managedIdentityId'] ) # Initialize graph instance diff --git a/build/core/build_utility.py b/build/core/build_utility.py index 28c0385a..4001bb88 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -27,7 +27,7 @@ class DockerClient: - def __init__(self, docker_registry, docker_namespace, docker_username, docker_password): + def __init__(self, docker_registry, docker_namespace, docker_username, docker_password, managed_identity_id=None): docker_registry = "" if docker_registry == "public" else docker_registry @@ -35,6 +35,7 @@ def __init__(self, docker_registry, docker_namespace, docker_username, docker_pa self.docker_namespace = docker_namespace self.docker_username = docker_username self.docker_password = docker_password + self.managed_identity_id = managed_identity_id self.docker_login() @@ -49,8 +50,29 @@ def resolve_image_name(self, image_name): def docker_login(self): - shell_cmd = "docker login -u {0} -p {1} {2}".format(self.docker_username, self.docker_password, self.docker_registry) - execute_shell(shell_cmd) + if self.docker_username and self.docker_password: + shell_cmd = "docker login -u {0} -p {1} {2}".format(self.docker_username, self.docker_password, self.docker_registry) + execute_shell(shell_cmd) + else: + # Check if already logged in to Azure CLI + try: + logger.info("Checking Azure CLI login status...") + subprocess.check_output("az account show", shell=True, stderr=subprocess.STDOUT) + logger.info("Azure CLI is already logged in") + except subprocess.CalledProcessError: + logger.info("Azure CLI not logged in, initiating login...") + if self.managed_identity_id: + # Login with managed identity + shell_cmd = "az login --identity --username {0}".format(self.managed_identity_id) + logger.info("Logging in with managed identity: {0}".format(self.managed_identity_id)) + else: + # Interactive login with user account + shell_cmd = "az login" + logger.info("Initiating interactive Azure login...") + execute_shell(shell_cmd) + + shell_cmd = "az acr login --name {0}".format(self.docker_registry) + execute_shell(shell_cmd) def docker_image_build(self, image_name, dockerfile_path, build_path): diff --git a/build/model/config_model.py b/build/model/config_model.py index 9d93c7c8..bff5fc6d 100644 --- a/build/model/config_model.py +++ b/build/model/config_model.py @@ -44,6 +44,8 @@ def build_config_parse(self): if "docker-username" in buildConfigContent["cluster"]["docker-registry-info"] else None self.buildConfigDict["dockerRegistryInfo"]["dockerPassword"] = buildConfigContent["cluster"]["docker-registry-info"]["docker-password"] \ if "docker-password" in buildConfigContent["cluster"]["docker-registry-info"] else None + self.buildConfigDict["dockerRegistryInfo"]["managedIdentityId"] = buildConfigContent["cluster"]["docker-registry-info"]["managed-identity-id"] \ + if "managed-identity-id" in buildConfigContent["cluster"]["docker-registry-info"] else None self.buildConfigDict["dockerRegistryInfo"]["dockerTag"] = \ buildConfigContent["cluster"]["docker-registry-info"]["docker-tag"] self.buildConfigDict["dockerRegistryInfo"]["secretName"] = \ @@ -59,6 +61,8 @@ def build_config_parse(self): if "username" in buildConfigContent["cluster"]["docker-registry"] else None self.buildConfigDict["dockerRegistryInfo"]["dockerPassword"] = buildConfigContent["cluster"]["docker-registry"]["password"] \ if "password" in buildConfigContent["cluster"]["docker-registry"] else None + self.buildConfigDict["dockerRegistryInfo"]["managedIdentityId"] = buildConfigContent["cluster"]["docker-registry"]["managed-identity-id"] \ + if "managed-identity-id" in buildConfigContent["cluster"]["docker-registry"] else None self.buildConfigDict["dockerRegistryInfo"]["dockerTag"] = \ buildConfigContent["cluster"]["docker-registry"]["tag"] self.buildConfigDict["dockerRegistryInfo"]["secretName"] = \ diff --git a/build/pai_build.py b/build/pai_build.py index be4218eb..6970a370 100755 --- a/build/pai_build.py +++ b/build/pai_build.py @@ -105,6 +105,11 @@ def main(): default=None, help="The image list you want to build" ) + build_parser.add_argument( + '--managed-identity-id', + type=str, + help="The Azure managed identity ID for authentication" + ) build_parser.set_defaults(func=build_service) # Push commands @@ -147,6 +152,11 @@ def main(): type=str, help="The docker password you want to use for authentication, which will override the config file if '--docker-registry' is also set" ) + push_parser.add_argument( + '--managed-identity-id', + type=str, + help="The Azure managed identity ID for authentication, which will override the config file if '--docker-registry' is also set" + ) push_parser.add_argument( "--docker-tag", type=str, @@ -156,15 +166,18 @@ def main(): args = parser.parse_args() config_model = load_build_config(args.config) + if hasattr(args, 'docker_registry') and args.docker_registry is not None: config_model['dockerRegistryInfo']['dockerRegistryDomain'] = args.docker_registry - if args.docker_namespace is not None: + if hasattr(args, 'docker_namespace') and args.docker_namespace is not None: config_model['dockerRegistryInfo']['dockerNameSpace'] = args.docker_namespace - if args.docker_username is not None: + if hasattr(args, 'docker_username') and args.docker_username is not None: config_model['dockerRegistryInfo']['dockerUserName'] = args.docker_username - if args.docker_password is not None: + if hasattr(args, 'docker_password') and args.docker_password is not None: config_model['dockerRegistryInfo']['dockerPassword'] = args.docker_password - if args.docker_tag is not None: + if hasattr(args, 'managed_identity_id') and args.managed_identity_id is not None: + config_model['dockerRegistryInfo']['managedIdentityId'] = args.managed_identity_id + if hasattr(args, 'docker_tag') and args.docker_tag is not None: config_model['dockerRegistryInfo']['dockerTag'] = args.docker_tag args.func(args, config_model) From fcb4454194535fbf192e35573556125cecfa993b Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Thu, 19 Mar 2026 04:19:47 +0000 Subject: [PATCH 2/6] add warnings when subscription is not correct --- build/core/build_utility.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/build/core/build_utility.py b/build/core/build_utility.py index 4001bb88..47745157 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -71,8 +71,22 @@ def docker_login(self): logger.info("Initiating interactive Azure login...") execute_shell(shell_cmd) + # Login to Azure Container Registry shell_cmd = "az acr login --name {0}".format(self.docker_registry) - execute_shell(shell_cmd) + try: + logger.info("Begin to execute the command: {0}".format(shell_cmd)) + subprocess.check_call(shell_cmd, shell=True) + logger.info("Executing command successfully: {0}".format(shell_cmd)) + except subprocess.CalledProcessError: + logger.error("Executing command failed: {0}".format(shell_cmd)) + logger.error("ACR login failed. This may be caused by:") + logger.error(" 1. Wrong Azure subscription is selected") + logger.error(" 2. ACR registry '{0}' not found in current subscription".format(self.docker_registry)) + logger.error(" 3. No permission to access the ACR registry") + logger.error("Please check your Azure subscription and permissions.") + logger.error("You can run 'az account show' to check current subscription,") + logger.error("and run 'az account set --subscription ' to switch subscription.") + sys.exit(1) def docker_image_build(self, image_name, dockerfile_path, build_path): From e66938b8cfd5b8a96f217ac8042eef4c8898d3e3 Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Thu, 19 Mar 2026 05:58:42 +0000 Subject: [PATCH 3/6] add acr checking in current subcription --- build/core/build_utility.py | 66 +++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/build/core/build_utility.py b/build/core/build_utility.py index 47745157..7c97f670 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -71,21 +71,61 @@ def docker_login(self): logger.info("Initiating interactive Azure login...") execute_shell(shell_cmd) + # Check if ACR exists in current subscription before attempting login + # Extract registry name without .azurecr.io suffix + registry_name = self.docker_registry.replace('.azurecr.io', '') + check_cmd = "az acr show --name {0}".format(registry_name) + + try: + logger.info("Checking if ACR '{0}' exists in current subscription...".format(registry_name)) + subprocess.run( + check_cmd, + shell=True, + capture_output=True, + text=True, + timeout=30, + check=True + ) + logger.info("ACR '{0}' found in current subscription".format(registry_name)) + except subprocess.TimeoutExpired: + logger.error("Command timed out: {0}".format(check_cmd)) + logger.error("Failed to check ACR existence within 10 seconds") + sys.exit(1) + except subprocess.CalledProcessError as e: + logger.error("ACR '{0}' not found in current subscription".format(registry_name)) + if e.stderr: + logger.error("Error details: {0}".format(e.stderr.strip())) + logger.error("") + logger.error("Please check:") + logger.error(" 1. The ACR registry name is correct") + logger.error(" 2. You are using the correct Azure subscription") + logger.error("") + logger.error("Current subscription can be checked with: az account show") + logger.error("To switch subscription, use: az account set --subscription ") + sys.exit(1) + # Login to Azure Container Registry - shell_cmd = "az acr login --name {0}".format(self.docker_registry) + shell_cmd = "az acr login --name {0}".format(registry_name) try: - logger.info("Begin to execute the command: {0}".format(shell_cmd)) - subprocess.check_call(shell_cmd, shell=True) - logger.info("Executing command successfully: {0}".format(shell_cmd)) - except subprocess.CalledProcessError: - logger.error("Executing command failed: {0}".format(shell_cmd)) - logger.error("ACR login failed. This may be caused by:") - logger.error(" 1. Wrong Azure subscription is selected") - logger.error(" 2. ACR registry '{0}' not found in current subscription".format(self.docker_registry)) - logger.error(" 3. No permission to access the ACR registry") - logger.error("Please check your Azure subscription and permissions.") - logger.error("You can run 'az account show' to check current subscription,") - logger.error("and run 'az account set --subscription ' to switch subscription.") + logger.info("Logging in to ACR '{0}'...".format(registry_name)) + subprocess.run( + shell_cmd, + shell=True, + capture_output=True, + text=True, + timeout=30, + check=True + ) + logger.info("Successfully logged in to ACR '{0}'".format(registry_name)) + except subprocess.TimeoutExpired: + logger.error("Command timed out: {0}".format(shell_cmd)) + logger.error("ACR login command exceeded 30 seconds timeout") + sys.exit(1) + except subprocess.CalledProcessError as e: + logger.error("Failed to login to ACR '{0}'".format(registry_name)) + if e.stderr: + logger.error("Error details: {0}".format(e.stderr.strip())) + logger.error("Please check your permissions to access this ACR registry") sys.exit(1) From b61ab17b8e5265edf676e44d7fd89c138b2ddd0c Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Fri, 20 Mar 2026 00:53:51 +0000 Subject: [PATCH 4/6] fix the problem to use identity ID for acr login --- build/core/build_utility.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/build/core/build_utility.py b/build/core/build_utility.py index 7c97f670..f1ff89f2 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -63,8 +63,19 @@ def docker_login(self): logger.info("Azure CLI not logged in, initiating login...") if self.managed_identity_id: # Login with managed identity - shell_cmd = "az login --identity --username {0}".format(self.managed_identity_id) - logger.info("Logging in with managed identity: {0}".format(self.managed_identity_id)) + # Please NOTE that the managed identity must have the following permissions to access the ACR registry including: + # ------ "Reader" for az show command to check ACR existence + # ------ "AcrPull" for az acr login and docker pull command + # ------ "AcrPush" if you want to push docker images to ACR registry as well + # Determine the type of managed identity identifier + if self.managed_identity_id.startswith('/subscriptions/'): + # Full resource ID format + shell_cmd = "az login --identity --resource-id {0}".format(self.managed_identity_id) + logger.info("Logging in with managed identity (resource-id): {0}".format(self.managed_identity_id)) + else: + # Assume it's a client ID (UUID format) + shell_cmd = "az login --identity --client-id {0}".format(self.managed_identity_id) + logger.info("Logging in with managed identity (client-id): {0}".format(self.managed_identity_id)) else: # Interactive login with user account shell_cmd = "az login" From a112e7209481c854c07979ecbb9fe5cf0ae5d960 Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Fri, 20 Mar 2026 02:07:13 +0000 Subject: [PATCH 5/6] remove docker login for build command --- build/core/build_center.py | 4 ++++ build/core/build_utility.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build/core/build_center.py b/build/core/build_center.py index 74d68bc2..c13e4209 100644 --- a/build/core/build_center.py +++ b/build/core/build_center.py @@ -159,6 +159,10 @@ def build_center(self): def push_center(self): + # Login to Docker registry before pushing images + self.logger.info("Preparing to push images, logging in to Docker registry...") + self.docker_cli.docker_login() + # Find services and map dockfile to services self.construct_graph() diff --git a/build/core/build_utility.py b/build/core/build_utility.py index f1ff89f2..61511e97 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -37,7 +37,8 @@ def __init__(self, docker_registry, docker_namespace, docker_username, docker_pa self.docker_password = docker_password self.managed_identity_id = managed_identity_id - self.docker_login() + # Login is no longer performed automatically during initialization + # Call docker_login() explicitly when needed (e.g., before pushing images) def set_build_cache_type(self, build_nocache=False): From f68ecefd79d1129bc2b4bb528abe4702eef2d32e Mon Sep 17 00:00:00 2001 From: Rui Gao Date: Fri, 20 Mar 2026 15:29:05 +0800 Subject: [PATCH 6/6] Update build/core/build_utility.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- build/core/build_utility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/core/build_utility.py b/build/core/build_utility.py index 61511e97..68ce7746 100644 --- a/build/core/build_utility.py +++ b/build/core/build_utility.py @@ -101,7 +101,7 @@ def docker_login(self): logger.info("ACR '{0}' found in current subscription".format(registry_name)) except subprocess.TimeoutExpired: logger.error("Command timed out: {0}".format(check_cmd)) - logger.error("Failed to check ACR existence within 10 seconds") + logger.error("Failed to check ACR existence within 30 seconds") sys.exit(1) except subprocess.CalledProcessError as e: logger.error("ACR '{0}' not found in current subscription".format(registry_name))