prefect-client 2.14.10__py3-none-any.whl → 2.14.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,8 +11,11 @@ Classes:
11
11
 
12
12
  """
13
13
  import json
14
+ import random
14
15
  import shlex
16
+ import string
15
17
  import subprocess
18
+ import time
16
19
  from copy import deepcopy
17
20
  from textwrap import dedent
18
21
  from typing import Any, Dict, Optional
@@ -23,12 +26,17 @@ from rich.console import Console
23
26
  from rich.panel import Panel
24
27
  from rich.progress import Progress, SpinnerColumn, TextColumn
25
28
  from rich.prompt import Confirm
29
+ from rich.syntax import Syntax
26
30
 
27
- from prefect.cli._prompts import prompt_select_from_table
31
+ from prefect.cli._prompts import prompt, prompt_select_from_table
28
32
  from prefect.client.orchestration import PrefectClient
29
33
  from prefect.client.schemas.actions import BlockDocumentCreate
30
34
  from prefect.client.utilities import inject_client
31
35
  from prefect.exceptions import ObjectAlreadyExists, ObjectNotFound
36
+ from prefect.settings import (
37
+ PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE,
38
+ update_current_profile,
39
+ )
32
40
 
33
41
 
34
42
  class AzureCLI:
@@ -124,7 +132,6 @@ class ContainerInstancePushProvisioner:
124
132
  _subscription_name (str): Azure subscription name.
125
133
  _resource_group (str): Azure resource group name.
126
134
  _location (str): Azure resource location.
127
- _container_image (str): Docker image for the container instance.
128
135
  azure_cli (AzureCLI): An instance of AzureCLI for running Azure commands.
129
136
 
130
137
  Methods:
@@ -136,22 +143,21 @@ class ContainerInstancePushProvisioner:
136
143
  _generate_secret_for_app: Generates a secret for the app registration.
137
144
  _get_service_principal_object_id: Retrieves the object ID of the service principal associated with the app registration.
138
145
  _assign_contributor_role: Assigns the Contributor role to the service account.
139
- _create_container_instance: Creates an Azure Container Instance.
140
146
  _create_aci_credentials_block: Creates an Azure Container Instance credentials block.
141
147
  provision: Orchestrates the provisioning of Azure resources and setup for the push work pool.
142
148
  """
143
149
 
144
- DEFAULT_LOCATION = "eastus"
145
- RESOURCE_GROUP_NAME = "prefect-aci-push-pool-rg"
146
- CONTAINER_IMAGE = "docker.io/prefecthq/prefect:2-latest"
147
- APP_REGISTRATION_NAME = "prefect-aci-push-pool-app"
148
-
149
150
  def __init__(self):
150
151
  self._console = Console()
151
152
  self._subscription_id = None
152
153
  self._subscription_name = None
153
- self._location = None
154
+ self._location = "eastus"
155
+ self._identity_name = "prefect-acr-identity"
154
156
  self.azure_cli = AzureCLI(self.console)
157
+ self._credentials_block_name = None
158
+ self._resource_group_name = "prefect-aci-push-pool-rg"
159
+ self._app_registration_name = "prefect-aci-push-pool-app"
160
+ self._registry_name_prefix = "prefect"
155
161
 
156
162
  @property
157
163
  def console(self) -> Console:
@@ -173,7 +179,8 @@ class ContainerInstancePushProvisioner:
173
179
  'az account list-locations --query "[?isDefault].name" --output tsv'
174
180
  )
175
181
  output = await self.azure_cli.run_command(command)
176
- self._location = output if output else self.DEFAULT_LOCATION
182
+ if output:
183
+ self._location = output
177
184
  except subprocess.CalledProcessError as e:
178
185
  raise RuntimeError("Failed to get default location.") from e
179
186
 
@@ -274,7 +281,7 @@ class ContainerInstancePushProvisioner:
274
281
  subprocess.CalledProcessError: If the Azure CLI command execution fails.
275
282
  """
276
283
  check_exists_command = (
277
- f"az group exists --name {self.RESOURCE_GROUP_NAME} --subscription"
284
+ f"az group exists --name {self._resource_group_name} --subscription"
278
285
  f" {self._subscription_id}"
279
286
  )
280
287
  exists_result = await self.azure_cli.run_command(
@@ -283,7 +290,7 @@ class ContainerInstancePushProvisioner:
283
290
  if exists_result is True:
284
291
  self._console.print(
285
292
  (
286
- f"Resource group '{self.RESOURCE_GROUP_NAME}' already exists in"
293
+ f"Resource group '{self._resource_group_name}' already exists in"
287
294
  f" subscription {self._subscription_name}."
288
295
  ),
289
296
  style="yellow",
@@ -291,16 +298,16 @@ class ContainerInstancePushProvisioner:
291
298
  return
292
299
 
293
300
  resource_group_command = (
294
- f"az group create --name '{self.RESOURCE_GROUP_NAME}' --location"
301
+ f"az group create --name '{self._resource_group_name}' --location"
295
302
  f" '{self._location}' --subscription '{self._subscription_id}'"
296
303
  )
297
304
  await self.azure_cli.run_command(
298
305
  resource_group_command,
299
306
  success_message=(
300
- f"Resource group '{self.RESOURCE_GROUP_NAME}' created successfully"
307
+ f"Resource group '{self._resource_group_name}' created successfully"
301
308
  ),
302
309
  failure_message=(
303
- f"Failed to create resource group '{self.RESOURCE_GROUP_NAME}' in"
310
+ f"Failed to create resource group '{self._resource_group_name}' in"
304
311
  f" subscription '{self._subscription_name}'"
305
312
  ),
306
313
  ignore_if_exists=True,
@@ -318,7 +325,7 @@ class ContainerInstancePushProvisioner:
318
325
  """
319
326
  # Check if the app registration already exists
320
327
  check_exists_command = (
321
- f"az ad app list --display-name {self.APP_REGISTRATION_NAME} --output json"
328
+ f"az ad app list --display-name {self._app_registration_name} --output json"
322
329
  )
323
330
  app_registrations = await self.azure_cli.run_command(
324
331
  check_exists_command,
@@ -331,29 +338,29 @@ class ContainerInstancePushProvisioner:
331
338
  (
332
339
  app
333
340
  for app in app_registrations
334
- if app["displayName"] == self.APP_REGISTRATION_NAME
341
+ if app["displayName"] == self._app_registration_name
335
342
  ),
336
343
  None,
337
344
  )
338
345
  if existing_app_registration:
339
346
  self._console.print(
340
- f"App registration '{self.APP_REGISTRATION_NAME}' already exists.",
347
+ f"App registration '{self._app_registration_name}' already exists.",
341
348
  style="yellow",
342
349
  )
343
350
  return existing_app_registration["appId"]
344
351
 
345
352
  app_registration_command = (
346
- f"az ad app create --display-name {self.APP_REGISTRATION_NAME} "
353
+ f"az ad app create --display-name {self._app_registration_name} "
347
354
  "--output json"
348
355
  )
349
356
  app_registration = await self.azure_cli.run_command(
350
357
  app_registration_command,
351
358
  success_message=(
352
- f"App registration '{self.APP_REGISTRATION_NAME}' created successfully"
359
+ f"App registration '{self._app_registration_name}' created successfully"
353
360
  ),
354
361
  failure_message=(
355
362
  "Failed to create app registration with name"
356
- f" '{self.APP_REGISTRATION_NAME}'"
363
+ f" '{self._app_registration_name}'"
357
364
  ),
358
365
  ignore_if_exists=True,
359
366
  )
@@ -389,8 +396,8 @@ class ContainerInstancePushProvisioner:
389
396
  failure_message=(
390
397
  "Failed to generate secret for app registration with client ID"
391
398
  f" '{app_id}'. If you have already generated 2 secrets for this app"
392
- " registration, please delete one from the `prefect-aci-push-pool-app`"
393
- " resource and try again."
399
+ " registration, please delete one from the"
400
+ f" `{self._app_registration_name}` resource and try again."
394
401
  ),
395
402
  ignore_if_exists=True,
396
403
  return_json=True,
@@ -449,7 +456,151 @@ class ContainerInstancePushProvisioner:
449
456
  f"Failed to retrieve new service principal for app ID {app_id}"
450
457
  )
451
458
 
452
- async def _assign_contributor_role(self, app_id: str) -> None:
459
+ async def _get_or_create_identity(
460
+ self, identity_name: str, resource_group_name: str, subscription_id: str
461
+ ):
462
+ """
463
+ Retrieves or creates a managed identity for the given resource group.
464
+
465
+ Returns:
466
+ dict: Object representing the identity.
467
+ """
468
+ # Try to retrieve the existing identity
469
+ command_get_identity = (
470
+ f"az identity list --query \"[?name=='{identity_name}']\" --resource-group"
471
+ f" {resource_group_name} --subscription {subscription_id} --output json"
472
+ )
473
+ identity = await self.azure_cli.run_command(
474
+ command_get_identity,
475
+ return_json=True,
476
+ )
477
+
478
+ if identity:
479
+ self._console.print(
480
+ (
481
+ f"Identity '{self._identity_name}' already exists in"
482
+ f" subscription '{self._subscription_name}'."
483
+ ),
484
+ style="yellow",
485
+ )
486
+ return identity[0]
487
+
488
+ # Identity does not exist, create it
489
+ command_create_identity = (
490
+ f"az identity create --name {identity_name} --resource-group"
491
+ f" {resource_group_name} --subscription {subscription_id} --output json"
492
+ )
493
+ response = await self.azure_cli.run_command(
494
+ command_create_identity,
495
+ success_message=f"Identity {identity_name!r} created",
496
+ failure_message=f"Failed to create identity {identity_name!r}",
497
+ return_json=True,
498
+ )
499
+
500
+ if response:
501
+ return response
502
+ else:
503
+ raise Exception(
504
+ f"Failed to retrieve new identity for identity {self._identity_name}"
505
+ )
506
+
507
+ @staticmethod
508
+ def _generate_acr_name(base_name: str):
509
+ # Ensure the base name adheres to ACR naming conventions
510
+ if not base_name.isalnum() or len(base_name) > 50:
511
+ raise ValueError(
512
+ "ACR registry name prefix should be alphanumeric and up to 50"
513
+ " characters"
514
+ )
515
+
516
+ # Generate a unique string
517
+ timestamp = int(time.time())
518
+ random_str = "".join(
519
+ random.choices(string.ascii_lowercase + string.digits, k=4)
520
+ )
521
+
522
+ # Combine to form the ACR name
523
+ acr_name = f"{base_name}{timestamp}{random_str}"
524
+ return acr_name
525
+
526
+ async def _get_or_create_registry(
527
+ self,
528
+ registry_name: str,
529
+ resource_group_name: str,
530
+ location: str,
531
+ subscription_id: str,
532
+ ):
533
+ """
534
+ Retrieves or creates an Azure Container Registry.
535
+
536
+ Args:
537
+ registry_name: The name of the registry.
538
+ resource_group_name: The name of the resource group to use for the registry.
539
+ location: Where to create the registry.
540
+ subscription_id: The ID of the subscription to use for the registry.
541
+
542
+ Returns:
543
+ dict: Object representing the registry.
544
+ """
545
+ # check to see if there are any registries starting with 'prefect'
546
+ command_get_registries = (
547
+ 'az acr list --query "[?starts_with(name,'
548
+ f" '{self._registry_name_prefix}')]\" --subscription"
549
+ f" {subscription_id} --output json"
550
+ )
551
+ response = await self.azure_cli.run_command(
552
+ command_get_registries,
553
+ return_json=True,
554
+ )
555
+
556
+ # acr names must be globally unique, so if there are any matches, use the first one
557
+ if response:
558
+ self._console.print(
559
+ (
560
+ f"Registry with prefix {self._registry_name_prefix!r} already"
561
+ f" exists in subscription '{subscription_id}'."
562
+ ),
563
+ style="yellow",
564
+ )
565
+ return response[0]
566
+
567
+ command_create_repository = (
568
+ f"az acr create --name {registry_name} --resource-group"
569
+ f" {resource_group_name} --subscription {subscription_id} --location"
570
+ f" {location} --sku Basic"
571
+ )
572
+ response = await self.azure_cli.run_command(
573
+ command_create_repository,
574
+ success_message="Registry created",
575
+ failure_message="Failed to create registry",
576
+ return_json=True,
577
+ )
578
+
579
+ if response:
580
+ return response
581
+ else:
582
+ raise Exception(f"Failed to create registry {registry_name}")
583
+
584
+ async def _log_into_registry(self, login_server: str, subscription_id: str):
585
+ """
586
+ Logs into the given Azure Container Registry.
587
+
588
+ Args:
589
+ registry_name: The name of the registry to log into.
590
+
591
+ Raises:
592
+ subprocess.CalledProcessError: If the Azure CLI command execution fails.
593
+ """
594
+ command_login = (
595
+ f"az acr login --name {login_server} --subscription {subscription_id}"
596
+ )
597
+ await self.azure_cli.run_command(
598
+ command_login,
599
+ success_message=f"Logged into registry {login_server}",
600
+ failure_message=f"Failed to log into registry {login_server}",
601
+ )
602
+
603
+ async def _assign_contributor_role(self, app_id: str, subscription_id: str) -> None:
453
604
  """
454
605
  Assigns the 'Contributor' role to the service principal associated with a given app ID.
455
606
 
@@ -464,17 +615,18 @@ class ContainerInstancePushProvisioner:
464
615
 
465
616
  if service_principal_id:
466
617
  role = "Contributor"
467
- scope = f"/subscriptions/{self._subscription_id}/resourceGroups/{self.RESOURCE_GROUP_NAME}"
618
+ scope = f"/subscriptions/{self._subscription_id}/resourceGroups/{self._resource_group_name}"
468
619
 
469
620
  # Check if the role is already assigned
470
621
  check_role_command = (
471
622
  f"az role assignment list --assignee {service_principal_id} --role"
472
- f" {role} --scope {scope} --output json"
623
+ f" {role} --scope {scope} --subscription {subscription_id} --output"
624
+ " json"
473
625
  )
474
626
  role_assignments = await self.azure_cli.run_command(
475
627
  check_role_command, return_json=True
476
628
  )
477
- if any(
629
+ if role_assignments and any(
478
630
  ra
479
631
  for ra in role_assignments
480
632
  if ra["roleDefinitionName"] == role and ra["scope"] == scope
@@ -490,7 +642,8 @@ class ContainerInstancePushProvisioner:
490
642
 
491
643
  assign_command = (
492
644
  f"az role assignment create --role {role} --assignee-object-id"
493
- f" {service_principal_id} --scope {scope}"
645
+ f" {service_principal_id} --scope {scope} --subscription"
646
+ f" {subscription_id}"
494
647
  )
495
648
  await self.azure_cli.run_command(
496
649
  assign_command,
@@ -505,60 +658,24 @@ class ContainerInstancePushProvisioner:
505
658
  ignore_if_exists=True,
506
659
  )
507
660
 
508
- async def _create_container_instance(self) -> None:
661
+ async def _assign_acr_pull_role(
662
+ self, identity: Dict[str, Any], registry: Dict[str, Any], subscription_id: str
663
+ ) -> None:
509
664
  """
510
- Creates an Azure Container Instance using predefined settings.
665
+ Assigns the AcrPull role to the specified identity for the given registry.
511
666
 
512
- Raises:
513
- subprocess.CalledProcessError: If the Azure CLI command execution fails.
667
+ Args:
668
+ identity: The identity to assign the role to.
669
+ registry: The registry to grant access to.
514
670
  """
515
- container_name = "prefect-aci-push-pool-container"
516
-
517
- check_exists_command = (
518
- "az container list --resource-group"
519
- f" {self.RESOURCE_GROUP_NAME} --subscription"
520
- f" {self._subscription_id} --query \"[?name=='{container_name}']\" --output"
521
- " json"
522
- )
523
-
524
- result = await self.azure_cli.run_command(
525
- check_exists_command,
526
- return_json=True,
527
- )
528
-
529
- if result:
530
- self._console.print(
531
- (
532
- f"Container instance '{container_name}' already exists in"
533
- f" subscription '{self._subscription_name}' in location"
534
- f" '{self._location}'."
535
- ),
536
- style="yellow",
537
- )
538
- return
539
-
540
- create_command = (
541
- f"az container create --name {container_name} "
542
- f"--resource-group {self.RESOURCE_GROUP_NAME} "
543
- "--image docker.io/prefecthq/prefect:2-latest "
544
- f"--location {self._location} --subscription {self._subscription_id} "
545
- "--restart-policy OnFailure --output json"
671
+ command = (
672
+ f"az role assignment create --assignee {identity['principalId']} --scope"
673
+ f" {registry['id']} --role AcrPull --subscription {subscription_id}"
546
674
  )
547
675
  await self.azure_cli.run_command(
548
- create_command,
549
- success_message=(
550
- f"Container instance '{container_name}' created successfully in"
551
- f" resource group '{self.RESOURCE_GROUP_NAME}' in location"
552
- f" '{self._location}' in subscription '{self._subscription_name}'"
553
- ),
554
- failure_message=(
555
- f"Failed to create container instance '{container_name}' in resource"
556
- f" group '{self.RESOURCE_GROUP_NAME}' in location '{self._location}' in"
557
- f" subscription '{self._subscription_name}'"
558
- ),
676
+ command,
559
677
  ignore_if_exists=True,
560
678
  )
561
- return
562
679
 
563
680
  async def _create_aci_credentials_block(
564
681
  self,
@@ -584,7 +701,7 @@ class ContainerInstancePushProvisioner:
584
701
  Raises:
585
702
  ObjectAlreadyExists: If a credentials block with the same name already exists.
586
703
  """
587
- credentials_block_name = f"{work_pool_name}-push-pool-credentials"
704
+ credentials_block_name = self._credentials_block_name
588
705
  credentials_block_type = await client.read_block_type_by_slug(
589
706
  "azure-container-instance-credentials"
590
707
  )
@@ -658,6 +775,84 @@ class ContainerInstancePushProvisioner:
658
775
  except ObjectNotFound:
659
776
  return False
660
777
 
778
+ def _validate_user_input(self, name):
779
+ if 2 < len(name) < 40 and name.isalnum():
780
+ return True
781
+ else:
782
+ return False
783
+
784
+ async def _create_provision_table(self, work_pool_name: str, client: PrefectClient):
785
+ return Panel(
786
+ dedent(
787
+ f"""\
788
+ Provisioning infrastructure for your work pool [blue]{work_pool_name}[/] will require:
789
+
790
+ Updates in subscription: [blue]{self._subscription_name}[/]
791
+
792
+ - Create a resource group in location: [blue]{self._location}[/]
793
+ - Create an app registration in Azure AD: [blue]{self._app_registration_name}[/]
794
+ - Create/use a service principal for app registration
795
+ - Generate a secret for app registration
796
+ - Create an Azure Container Registry with prefix [blue]{self._registry_name_prefix}[/]
797
+ - Create an identity [blue]{self._identity_name}[/] to allow access to the created registry
798
+ - Assign Contributor role to service account
799
+ - Create an ACR registry for image hosting
800
+ - Create an identity for Azure Container Instance to allow access to the registry
801
+
802
+ Updates in Prefect workspace
803
+
804
+ - Create Azure Container Instance credentials block: [blue]{self._credentials_block_name}[/]
805
+ """
806
+ ),
807
+ expand=False,
808
+ )
809
+
810
+ async def _customize_resource_names(
811
+ self, work_pool_name: str, client: PrefectClient
812
+ ) -> bool:
813
+ self._resource_group_name = prompt(
814
+ "Please enter a name for the resource group",
815
+ default=self._resource_group_name,
816
+ )
817
+ self._app_registration_name = prompt(
818
+ "Please enter a name for the app registration",
819
+ default=self._app_registration_name,
820
+ )
821
+ while True:
822
+ self._registry_name_prefix = prompt(
823
+ "Please enter a prefix for the Azure Container Registry",
824
+ default=self._registry_name_prefix,
825
+ )
826
+ if self._validate_user_input(self._registry_name_prefix):
827
+ break
828
+ else:
829
+ self._console.print(
830
+ "The prefix must be alphanumeric and between 3-50 characters.",
831
+ style="red",
832
+ )
833
+ while True:
834
+ self._identity_name = prompt(
835
+ "Please enter a name for the identity (used for ACR access)",
836
+ default=self._identity_name,
837
+ )
838
+ if self._validate_user_input(self._identity_name):
839
+ break
840
+ else:
841
+ self._console.print(
842
+ "The identity name must be alphanumeric and at least 3 characters.",
843
+ style="red",
844
+ )
845
+ self._credentials_block_name = prompt(
846
+ "Please enter a name for the ACI credentials block",
847
+ default=self._credentials_block_name,
848
+ )
849
+ table = await self._create_provision_table(work_pool_name, client)
850
+ self._console.print(table)
851
+
852
+ return Confirm.ask(
853
+ "Proceed with infrastructure provisioning?", console=self._console
854
+ )
855
+
661
856
  @inject_client
662
857
  async def provision(
663
858
  self,
@@ -689,58 +884,68 @@ class ContainerInstancePushProvisioner:
689
884
  await self._verify_az_ready()
690
885
  await self._select_subscription()
691
886
  await self.set_location()
887
+ self._credentials_block_name = f"{work_pool_name}-push-pool-credentials"
692
888
 
693
- table = Panel(
694
- dedent(
695
- f"""\
696
- Provisioning infrastructure for your work pool [blue]{work_pool_name}[/] will require:
697
-
698
- Updates in subscription [blue]{self._subscription_name}[/]
699
-
700
- - Create a resource group in location [blue]{self._location}[/]
701
- - Create an app registration in Azure AD [blue]{self.APP_REGISTRATION_NAME}[/]
702
- - Create/use a service principal for app registration
703
- - Generate a secret for app registration
704
- - Assign Contributor role to service account
705
- - Create Azure Container Instance 'aci-push-pool-container' in resource group [blue]{self.RESOURCE_GROUP_NAME}[/]
706
-
707
- Updates in Prefect workspace
708
-
709
- - Create Azure Container Instance credentials block [blue]aci-push-pool-credentials[/]
710
- """
711
- ),
712
- expand=False,
713
- )
889
+ table = await self._create_provision_table(work_pool_name, client)
714
890
  self._console.print(table)
715
891
  if self._console.is_interactive:
716
- if not Confirm.ask(
717
- "Proceed with infrastructure provisioning?", console=self._console
892
+ chosen_option = prompt_select_from_table(
893
+ self._console,
894
+ "Proceed with infrastructure provisioning with default resource names?",
895
+ [
896
+ {"header": "Options:", "key": "option"},
897
+ ],
898
+ [
899
+ {
900
+ "option": (
901
+ "Yes, proceed with infrastructure provisioning with default"
902
+ " resource names"
903
+ )
904
+ },
905
+ {"option": "Customize resource names"},
906
+ {"option": "Do not proceed with infrastructure provisioning"},
907
+ ],
908
+ )
909
+ if chosen_option["option"] == "Customize resource names":
910
+ if not await self._customize_resource_names(work_pool_name, client):
911
+ return base_job_template
912
+
913
+ elif (
914
+ chosen_option["option"]
915
+ == "Do not proceed with infrastructure provisioning"
718
916
  ):
719
917
  return base_job_template
918
+ elif (
919
+ chosen_option["option"]
920
+ != "Yes, proceed with infrastructure provisioning with default"
921
+ " resource names"
922
+ ):
923
+ # basically, we should never hit this. i'm concerned that we might change
924
+ # the options in the future and forget to update this check
925
+ raise ValueError(f"Invalid option selected: {chosen_option['option']}")
720
926
 
721
927
  credentials_block_exists = await self._aci_credentials_block_exists(
722
- block_name=f"{work_pool_name}-push-pool-credentials", client=client
928
+ block_name=self._credentials_block_name, client=client
723
929
  )
724
930
 
725
931
  if not credentials_block_exists:
726
- total_tasks = 6
932
+ total_tasks = 7
727
933
  else:
728
- total_tasks = 5
934
+ total_tasks = 6
729
935
 
730
936
  with Progress(console=self._console) as progress:
731
- task = progress.add_task(
732
- "Provisioning infrastructure...", total=total_tasks
733
- )
734
- progress.console.print("Creating resource group..")
937
+ self.azure_cli._console = progress.console
938
+ task = progress.add_task("Provisioning infrastructure.", total=total_tasks)
939
+ progress.console.print("Creating resource group")
735
940
  await self._create_resource_group()
736
941
  progress.advance(task)
737
942
 
738
- progress.console.print("Creating app registration..")
943
+ progress.console.print("Creating app registration")
739
944
  client_id = await self._create_app_registration()
740
945
  progress.advance(task)
741
946
 
742
947
  credentials_block_exists = await self._aci_credentials_block_exists(
743
- block_name=f"{work_pool_name}-push-pool-credentials", client=client
948
+ block_name=self._credentials_block_name, client=client
744
949
  )
745
950
 
746
951
  if not credentials_block_exists:
@@ -750,7 +955,7 @@ class ContainerInstancePushProvisioner:
750
955
  )
751
956
  progress.advance(task)
752
957
 
753
- progress.console.print("Creating ACI credentials block..")
958
+ progress.console.print("Creating ACI credentials block")
754
959
  block_doc_id = await self._create_aci_credentials_block(
755
960
  work_pool_name, client_id, tenant_id, client_secret, client
756
961
  )
@@ -760,18 +965,49 @@ class ContainerInstancePushProvisioner:
760
965
  "ACI credentials block already exists.", style="yellow"
761
966
  )
762
967
  block_doc = await client.read_block_document_by_name(
763
- name=f"{work_pool_name}-push-pool-credentials",
968
+ name=self._credentials_block_name,
764
969
  block_type_slug="azure-container-instance-credentials",
765
970
  )
766
971
  block_doc_id = block_doc.id
767
972
  progress.advance(task)
768
973
 
769
- progress.console.print("Assigning Contributor role to service account...")
770
- await self._assign_contributor_role(app_id=client_id)
974
+ progress.console.print("Assigning Contributor role to service account")
975
+ await self._assign_contributor_role(
976
+ app_id=client_id, subscription_id=self._subscription_id
977
+ )
771
978
  progress.advance(task)
772
979
 
773
- progress.console.print("Creating Azure Container Instance..")
774
- await self._create_container_instance()
980
+ progress.console.print(
981
+ "Creating Azure Container Registry (this make take a few minutes)"
982
+ )
983
+
984
+ registry_name = self._generate_acr_name(self._registry_name_prefix)
985
+ registry = await self._get_or_create_registry(
986
+ registry_name=registry_name,
987
+ resource_group_name=self._resource_group_name,
988
+ location=self._location,
989
+ subscription_id=self._subscription_id,
990
+ )
991
+ await self._log_into_registry(
992
+ login_server=registry["loginServer"],
993
+ subscription_id=self._subscription_id,
994
+ )
995
+ update_current_profile(
996
+ {PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE: registry["loginServer"]}
997
+ )
998
+ progress.advance(task)
999
+
1000
+ progress.console.print("Creating identity")
1001
+ identity = await self._get_or_create_identity(
1002
+ identity_name=self._identity_name,
1003
+ resource_group_name=self._resource_group_name,
1004
+ subscription_id=self._subscription_id,
1005
+ )
1006
+ await self._assign_acr_pull_role(
1007
+ identity=identity,
1008
+ registry=registry,
1009
+ subscription_id=self._subscription_id,
1010
+ )
775
1011
  progress.advance(task)
776
1012
 
777
1013
  base_job_template_copy = deepcopy(base_job_template)
@@ -781,11 +1017,58 @@ class ContainerInstancePushProvisioner:
781
1017
 
782
1018
  base_job_template_copy["variables"]["properties"]["resource_group_name"][
783
1019
  "default"
784
- ] = self.RESOURCE_GROUP_NAME
1020
+ ] = self._resource_group_name
785
1021
 
786
1022
  base_job_template_copy["variables"]["properties"]["subscription_id"][
787
1023
  "default"
788
1024
  ] = self._subscription_id
1025
+ base_job_template_copy["variables"]["properties"]["image_registry"][
1026
+ "default"
1027
+ ] = {
1028
+ "registry_url": registry["loginServer"],
1029
+ "identity": identity["id"],
1030
+ }
1031
+ base_job_template_copy["variables"]["properties"]["identities"]["default"] = [
1032
+ identity["id"]
1033
+ ]
1034
+
1035
+ self._console.print(
1036
+ dedent(
1037
+ f"""\
1038
+ Your default Docker build namespace has been set to [blue]{registry["loginServer"]!r}[/].
1039
+ Use any image name to build and push to this registry by default:
1040
+ """
1041
+ ),
1042
+ Panel(
1043
+ Syntax(
1044
+ dedent(
1045
+ f"""\
1046
+ from prefect import flow
1047
+ from prefect.deployments import DeploymentImage
1048
+
1049
+
1050
+ @flow(log_prints=True)
1051
+ def my_flow(name: str = "world"):
1052
+ print(f"Hello {{name}}! I'm a flow running on an Azure Container Instance!")
1053
+
1054
+
1055
+ if __name__ == "__main__":
1056
+ my_flow.deploy(
1057
+ name="my-deployment",
1058
+ work_pool_name="{work_pool_name}",
1059
+ image=DeploymentImage(
1060
+ name="my-image:latest",
1061
+ platform="linux/amd64",
1062
+ )
1063
+ )"""
1064
+ ),
1065
+ "python",
1066
+ background_color="default",
1067
+ ),
1068
+ title="example_deploy_script.py",
1069
+ expand=False,
1070
+ ),
1071
+ )
789
1072
 
790
1073
  self._console.print(
791
1074
  (