Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion build/core/build_center.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -158,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()

Expand Down
96 changes: 92 additions & 4 deletions build/core/build_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@

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

self.docker_registry = docker_registry
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()
# 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):
Expand All @@ -49,8 +51,94 @@ 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
# 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"
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)

Comment thread
hippogr marked this conversation as resolved.
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 30 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 <subscription-id>")
sys.exit(1)

# Login to Azure Container Registry
shell_cmd = "az acr login --name {0}".format(registry_name)
try:
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)


def docker_image_build(self, image_name, dockerfile_path, build_path):
Expand Down
4 changes: 4 additions & 0 deletions build/model/config_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = \
Expand All @@ -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"] = \
Expand Down
21 changes: 17 additions & 4 deletions build/pai_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
hippogr marked this conversation as resolved.

# Push commands
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down