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.
- prefect/_internal/pydantic/v2_schema.py +9 -2
- prefect/client/orchestration.py +50 -2
- prefect/client/schemas/objects.py +16 -1
- prefect/deployments/runner.py +29 -2
- prefect/engine.py +128 -16
- prefect/flows.py +16 -0
- prefect/infrastructure/kubernetes.py +64 -0
- prefect/infrastructure/provisioners/cloud_run.py +199 -33
- prefect/infrastructure/provisioners/container_instance.py +398 -115
- prefect/infrastructure/provisioners/ecs.py +483 -48
- prefect/input/__init__.py +11 -0
- prefect/input/actions.py +88 -0
- prefect/input/run_input.py +107 -0
- prefect/runner/runner.py +5 -0
- prefect/runner/server.py +92 -8
- prefect/runner/utils.py +92 -0
- prefect/settings.py +34 -9
- prefect/utilities/dockerutils.py +31 -0
- prefect/utilities/processutils.py +5 -2
- prefect/utilities/validation.py +63 -0
- prefect/workers/utilities.py +0 -1
- {prefect_client-2.14.10.dist-info → prefect_client-2.14.11.dist-info}/METADATA +1 -1
- {prefect_client-2.14.10.dist-info → prefect_client-2.14.11.dist-info}/RECORD +26 -21
- {prefect_client-2.14.10.dist-info → prefect_client-2.14.11.dist-info}/LICENSE +0 -0
- {prefect_client-2.14.10.dist-info → prefect_client-2.14.11.dist-info}/WHEEL +0 -0
- {prefect_client-2.14.10.dist-info → prefect_client-2.14.11.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
|
|
1
|
+
import base64
|
1
2
|
import contextlib
|
2
3
|
import contextvars
|
3
4
|
import importlib
|
@@ -16,11 +17,17 @@ from rich.console import Console
|
|
16
17
|
from rich.panel import Panel
|
17
18
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
18
19
|
from rich.prompt import Confirm
|
20
|
+
from rich.syntax import Syntax
|
19
21
|
|
22
|
+
from prefect.cli._prompts import prompt
|
20
23
|
from prefect.client.orchestration import PrefectClient
|
21
24
|
from prefect.client.schemas.actions import BlockDocumentCreate
|
22
25
|
from prefect.client.utilities import inject_client
|
23
26
|
from prefect.exceptions import ObjectNotFound
|
27
|
+
from prefect.settings import (
|
28
|
+
PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE,
|
29
|
+
update_current_profile,
|
30
|
+
)
|
24
31
|
from prefect.utilities.collections import get_from_dict
|
25
32
|
from prefect.utilities.importtools import lazy_import
|
26
33
|
|
@@ -48,46 +55,10 @@ class IamPolicyResource:
|
|
48
55
|
|
49
56
|
def __init__(
|
50
57
|
self,
|
51
|
-
policy_name: str
|
58
|
+
policy_name: str,
|
52
59
|
):
|
53
60
|
self._iam_client = boto3.client("iam")
|
54
61
|
self._policy_name = policy_name
|
55
|
-
self._policy_document = json.dumps(
|
56
|
-
{
|
57
|
-
"Version": "2012-10-17",
|
58
|
-
"Statement": [
|
59
|
-
{
|
60
|
-
"Sid": "PrefectEcsPolicy",
|
61
|
-
"Effect": "Allow",
|
62
|
-
"Action": [
|
63
|
-
"ec2:AuthorizeSecurityGroupIngress",
|
64
|
-
"ec2:CreateSecurityGroup",
|
65
|
-
"ec2:CreateTags",
|
66
|
-
"ec2:DescribeNetworkInterfaces",
|
67
|
-
"ec2:DescribeSecurityGroups",
|
68
|
-
"ec2:DescribeSubnets",
|
69
|
-
"ec2:DescribeVpcs",
|
70
|
-
"ecs:CreateCluster",
|
71
|
-
"ecs:DeregisterTaskDefinition",
|
72
|
-
"ecs:DescribeClusters",
|
73
|
-
"ecs:DescribeTaskDefinition",
|
74
|
-
"ecs:DescribeTasks",
|
75
|
-
"ecs:ListAccountSettings",
|
76
|
-
"ecs:ListClusters",
|
77
|
-
"ecs:ListTaskDefinitions",
|
78
|
-
"ecs:RegisterTaskDefinition",
|
79
|
-
"ecs:RunTask",
|
80
|
-
"ecs:StopTask",
|
81
|
-
"logs:CreateLogStream",
|
82
|
-
"logs:PutLogEvents",
|
83
|
-
"logs:DescribeLogGroups",
|
84
|
-
"logs:GetLogEvents",
|
85
|
-
],
|
86
|
-
"Resource": "*",
|
87
|
-
}
|
88
|
-
],
|
89
|
-
}
|
90
|
-
)
|
91
62
|
|
92
63
|
self._requires_provisioning = None
|
93
64
|
|
@@ -146,6 +117,7 @@ class IamPolicyResource:
|
|
146
117
|
|
147
118
|
async def provision(
|
148
119
|
self,
|
120
|
+
policy_document: Dict[str, Any],
|
149
121
|
advance: Callable[[], None],
|
150
122
|
):
|
151
123
|
"""
|
@@ -164,13 +136,23 @@ class IamPolicyResource:
|
|
164
136
|
partial(
|
165
137
|
self._iam_client.create_policy,
|
166
138
|
PolicyName=self._policy_name,
|
167
|
-
PolicyDocument=
|
139
|
+
PolicyDocument=json.dumps(policy_document),
|
168
140
|
)
|
169
141
|
)
|
170
142
|
policy_arn = policy["Policy"]["Arn"]
|
171
143
|
advance()
|
172
144
|
return policy_arn
|
173
|
-
|
145
|
+
else:
|
146
|
+
policy = await anyio.to_thread.run_sync(
|
147
|
+
partial(self._get_policy_by_name, self._policy_name)
|
148
|
+
)
|
149
|
+
# This should never happen, but just in case
|
150
|
+
assert policy is not None, "Could not find expected policy"
|
151
|
+
return policy["Arn"]
|
152
|
+
|
153
|
+
@property
|
154
|
+
def next_steps(self):
|
155
|
+
return []
|
174
156
|
|
175
157
|
|
176
158
|
class IamUserResource:
|
@@ -246,6 +228,10 @@ class IamUserResource:
|
|
246
228
|
)
|
247
229
|
advance()
|
248
230
|
|
231
|
+
@property
|
232
|
+
def next_steps(self):
|
233
|
+
return []
|
234
|
+
|
249
235
|
|
250
236
|
class CredentialsBlockResource:
|
251
237
|
def __init__(self, user_name: str, block_document_name: str):
|
@@ -368,6 +354,10 @@ class CredentialsBlockResource:
|
|
368
354
|
"$ref": {"block_document_id": str(block_doc.id)}
|
369
355
|
}
|
370
356
|
|
357
|
+
@property
|
358
|
+
def next_steps(self):
|
359
|
+
return []
|
360
|
+
|
371
361
|
|
372
362
|
class AuthenticationResource:
|
373
363
|
def __init__(
|
@@ -375,18 +365,58 @@ class AuthenticationResource:
|
|
375
365
|
work_pool_name: str,
|
376
366
|
user_name: str = "prefect-ecs-user",
|
377
367
|
policy_name: str = "prefect-ecs-policy",
|
368
|
+
credentials_block_name: str = None,
|
378
369
|
):
|
379
370
|
self._user_name = user_name
|
371
|
+
self._credentials_block_name = (
|
372
|
+
credentials_block_name or f"{work_pool_name}-aws-credentials"
|
373
|
+
)
|
380
374
|
self._policy_name = policy_name
|
375
|
+
self._policy_document = {
|
376
|
+
"Version": "2012-10-17",
|
377
|
+
"Statement": [
|
378
|
+
{
|
379
|
+
"Sid": "PrefectEcsPolicy",
|
380
|
+
"Effect": "Allow",
|
381
|
+
"Action": [
|
382
|
+
"ec2:AuthorizeSecurityGroupIngress",
|
383
|
+
"ec2:CreateSecurityGroup",
|
384
|
+
"ec2:CreateTags",
|
385
|
+
"ec2:DescribeNetworkInterfaces",
|
386
|
+
"ec2:DescribeSecurityGroups",
|
387
|
+
"ec2:DescribeSubnets",
|
388
|
+
"ec2:DescribeVpcs",
|
389
|
+
"ecs:CreateCluster",
|
390
|
+
"ecs:DeregisterTaskDefinition",
|
391
|
+
"ecs:DescribeClusters",
|
392
|
+
"ecs:DescribeTaskDefinition",
|
393
|
+
"ecs:DescribeTasks",
|
394
|
+
"ecs:ListAccountSettings",
|
395
|
+
"ecs:ListClusters",
|
396
|
+
"ecs:ListTaskDefinitions",
|
397
|
+
"ecs:RegisterTaskDefinition",
|
398
|
+
"ecs:RunTask",
|
399
|
+
"ecs:StopTask",
|
400
|
+
"logs:CreateLogStream",
|
401
|
+
"logs:PutLogEvents",
|
402
|
+
"logs:DescribeLogGroups",
|
403
|
+
"logs:GetLogEvents",
|
404
|
+
],
|
405
|
+
"Resource": "*",
|
406
|
+
}
|
407
|
+
],
|
408
|
+
}
|
381
409
|
self._iam_user_resource = IamUserResource(user_name=user_name)
|
382
410
|
self._iam_policy_resource = IamPolicyResource(policy_name=policy_name)
|
383
411
|
self._credentials_block_resource = CredentialsBlockResource(
|
384
|
-
user_name=user_name, block_document_name=
|
412
|
+
user_name=user_name, block_document_name=self._credentials_block_name
|
385
413
|
)
|
414
|
+
self._execution_role_resource = ExecutionRoleResource()
|
386
415
|
|
387
416
|
@property
|
388
417
|
def resources(self):
|
389
418
|
return [
|
419
|
+
self._execution_role_resource,
|
390
420
|
self._iam_user_resource,
|
391
421
|
self._iam_policy_resource,
|
392
422
|
self._credentials_block_resource,
|
@@ -439,10 +469,25 @@ class AuthenticationResource:
|
|
439
469
|
infrastructure for.
|
440
470
|
advance: A callback function to indicate progress.
|
441
471
|
"""
|
472
|
+
# Provision task execution role
|
473
|
+
role_arn = await self._execution_role_resource.provision(
|
474
|
+
base_job_template=base_job_template, advance=advance
|
475
|
+
)
|
476
|
+
# Update policy document with the role ARN
|
477
|
+
self._policy_document["Statement"].append(
|
478
|
+
{
|
479
|
+
"Sid": "AllowPassRoleForEcs",
|
480
|
+
"Effect": "Allow",
|
481
|
+
"Action": "iam:PassRole",
|
482
|
+
"Resource": role_arn,
|
483
|
+
}
|
484
|
+
)
|
442
485
|
# Provision the IAM user
|
443
486
|
await self._iam_user_resource.provision(advance=advance)
|
444
487
|
# Provision the IAM policy
|
445
|
-
policy_arn = await self._iam_policy_resource.provision(
|
488
|
+
policy_arn = await self._iam_policy_resource.provision(
|
489
|
+
policy_document=self._policy_document, advance=advance
|
490
|
+
)
|
446
491
|
# Attach the policy to the user
|
447
492
|
if policy_arn:
|
448
493
|
iam_client = boto3.client("iam")
|
@@ -458,6 +503,14 @@ class AuthenticationResource:
|
|
458
503
|
advance=advance,
|
459
504
|
)
|
460
505
|
|
506
|
+
@property
|
507
|
+
def next_steps(self):
|
508
|
+
return [
|
509
|
+
next_step
|
510
|
+
for resource in self.resources
|
511
|
+
for next_step in resource.next_steps
|
512
|
+
]
|
513
|
+
|
461
514
|
|
462
515
|
class ClusterResource:
|
463
516
|
def __init__(self, cluster_name: str = "prefect-ecs-cluster"):
|
@@ -535,13 +588,22 @@ class ClusterResource:
|
|
535
588
|
"default"
|
536
589
|
] = self._cluster_name
|
537
590
|
|
591
|
+
@property
|
592
|
+
def next_steps(self):
|
593
|
+
return []
|
594
|
+
|
538
595
|
|
539
596
|
class VpcResource:
|
540
|
-
def __init__(
|
597
|
+
def __init__(
|
598
|
+
self,
|
599
|
+
vpc_name: str = "prefect-ecs-vpc",
|
600
|
+
ecs_security_group_name: str = "prefect-ecs-security-group",
|
601
|
+
):
|
541
602
|
self._ec2_client = boto3.client("ec2")
|
542
603
|
self._ec2_resource = boto3.resource("ec2")
|
543
604
|
self._vpc_name = vpc_name
|
544
605
|
self._requires_provisioning = None
|
606
|
+
self._ecs_security_group_name = ecs_security_group_name
|
545
607
|
|
546
608
|
async def get_task_count(self):
|
547
609
|
"""
|
@@ -746,7 +808,7 @@ class VpcResource:
|
|
746
808
|
await anyio.to_thread.run_sync(
|
747
809
|
partial(
|
748
810
|
self._ec2_resource.create_security_group,
|
749
|
-
GroupName=
|
811
|
+
GroupName=self._ecs_security_group_name,
|
750
812
|
Description=(
|
751
813
|
"Block all inbound traffic and allow all outbound traffic"
|
752
814
|
),
|
@@ -762,6 +824,269 @@ class VpcResource:
|
|
762
824
|
vpc.id
|
763
825
|
)
|
764
826
|
|
827
|
+
@property
|
828
|
+
def next_steps(self):
|
829
|
+
return []
|
830
|
+
|
831
|
+
|
832
|
+
class ContainerRepositoryResource:
|
833
|
+
def __init__(self, work_pool_name: str, repository_name: str = "prefect-flows"):
|
834
|
+
self._ecr_client = boto3.client("ecr")
|
835
|
+
self._repository_name = repository_name
|
836
|
+
self._requires_provisioning = None
|
837
|
+
self._work_pool_name = work_pool_name
|
838
|
+
self._next_steps = []
|
839
|
+
|
840
|
+
async def get_task_count(self):
|
841
|
+
"""
|
842
|
+
Returns the number of tasks that will be executed to provision this resource.
|
843
|
+
|
844
|
+
Returns:
|
845
|
+
int: The number of tasks to be provisioned.
|
846
|
+
"""
|
847
|
+
return 3 if await self.requires_provisioning() else 0
|
848
|
+
|
849
|
+
async def _get_prefect_created_registry(self):
|
850
|
+
try:
|
851
|
+
registries = await anyio.to_thread.run_sync(
|
852
|
+
partial(
|
853
|
+
self._ecr_client.describe_repositories,
|
854
|
+
repositoryNames=[self._repository_name],
|
855
|
+
)
|
856
|
+
)
|
857
|
+
return next(iter(registries), None)
|
858
|
+
except self._ecr_client.exceptions.RepositoryNotFoundException:
|
859
|
+
return None
|
860
|
+
|
861
|
+
async def requires_provisioning(self) -> bool:
|
862
|
+
"""
|
863
|
+
Check if this resource requires provisioning.
|
864
|
+
|
865
|
+
Returns:
|
866
|
+
bool: True if provisioning is required, False otherwise.
|
867
|
+
"""
|
868
|
+
if self._requires_provisioning is not None:
|
869
|
+
return self._requires_provisioning
|
870
|
+
|
871
|
+
if await self._get_prefect_created_registry() is not None:
|
872
|
+
self._requires_provisioning = False
|
873
|
+
return False
|
874
|
+
|
875
|
+
self._requires_provisioning = True
|
876
|
+
return True
|
877
|
+
|
878
|
+
async def get_planned_actions(self) -> List[str]:
|
879
|
+
"""
|
880
|
+
Returns a description of the planned actions for provisioning this resource.
|
881
|
+
|
882
|
+
Returns:
|
883
|
+
Optional[str]: A description of the planned actions for provisioning the resource,
|
884
|
+
or None if provisioning is not required.
|
885
|
+
"""
|
886
|
+
if await self.requires_provisioning():
|
887
|
+
return [
|
888
|
+
"Creating an ECR repository for storing Prefect images:"
|
889
|
+
f" [blue]{self._repository_name}[/]"
|
890
|
+
]
|
891
|
+
return []
|
892
|
+
|
893
|
+
async def provision(
|
894
|
+
self,
|
895
|
+
base_job_template: Dict[str, Any],
|
896
|
+
advance: Callable[[], None],
|
897
|
+
):
|
898
|
+
"""
|
899
|
+
Provisions an ECR repository.
|
900
|
+
|
901
|
+
Args:
|
902
|
+
base_job_template: The base job template of the work pool to provision
|
903
|
+
infrastructure for.
|
904
|
+
advance: A callback function to indicate progress.
|
905
|
+
"""
|
906
|
+
if await self.requires_provisioning():
|
907
|
+
console = current_console.get()
|
908
|
+
console.print("Provisioning ECR repository")
|
909
|
+
response = await anyio.to_thread.run_sync(
|
910
|
+
partial(
|
911
|
+
self._ecr_client.create_repository,
|
912
|
+
repositoryName=self._repository_name,
|
913
|
+
)
|
914
|
+
)
|
915
|
+
advance()
|
916
|
+
console.print("Authenticating with ECR")
|
917
|
+
auth_token = self._ecr_client.get_authorization_token()
|
918
|
+
user, passwd = (
|
919
|
+
base64.b64decode(
|
920
|
+
auth_token["authorizationData"][0]["authorizationToken"]
|
921
|
+
)
|
922
|
+
.decode()
|
923
|
+
.split(":")
|
924
|
+
)
|
925
|
+
proxy_endpoint = auth_token["authorizationData"][0]["proxyEndpoint"]
|
926
|
+
await run_process(f"docker login -u {user} -p {passwd} {proxy_endpoint}")
|
927
|
+
advance()
|
928
|
+
console.print("Setting default Docker build namespace")
|
929
|
+
namespace = response["repository"]["repositoryUri"].split("/")[0]
|
930
|
+
update_current_profile({PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE: namespace})
|
931
|
+
self._update_next_steps(namespace)
|
932
|
+
advance()
|
933
|
+
|
934
|
+
def _update_next_steps(self, repository_uri: str):
|
935
|
+
self._next_steps.extend(
|
936
|
+
[
|
937
|
+
dedent(
|
938
|
+
f"""\
|
939
|
+
|
940
|
+
Your default Docker build namespace has been set to [blue]{repository_uri!r}[/].
|
941
|
+
|
942
|
+
To build and push a Docker image to your newly created repository, use [blue]{self._repository_name!r}[/] as your image name:
|
943
|
+
"""
|
944
|
+
),
|
945
|
+
Panel(
|
946
|
+
Syntax(
|
947
|
+
dedent(
|
948
|
+
f"""\
|
949
|
+
from prefect import flow
|
950
|
+
from prefect.deployments import DeploymentImage
|
951
|
+
|
952
|
+
|
953
|
+
@flow(log_prints=True)
|
954
|
+
def my_flow(name: str = "world"):
|
955
|
+
print(f"Hello {{name}}! I'm a flow running on ECS!")
|
956
|
+
|
957
|
+
|
958
|
+
if __name__ == "__main__":
|
959
|
+
my_flow.deploy(
|
960
|
+
name="my-deployment",
|
961
|
+
work_pool_name="{self._work_pool_name}",
|
962
|
+
image=DeploymentImage(
|
963
|
+
name="{self._repository_name}:latest",
|
964
|
+
platform="linux/amd64",
|
965
|
+
)
|
966
|
+
)"""
|
967
|
+
),
|
968
|
+
"python",
|
969
|
+
background_color="default",
|
970
|
+
),
|
971
|
+
title="example_deploy_script.py",
|
972
|
+
expand=False,
|
973
|
+
),
|
974
|
+
]
|
975
|
+
)
|
976
|
+
|
977
|
+
@property
|
978
|
+
def next_steps(self):
|
979
|
+
return self._next_steps
|
980
|
+
|
981
|
+
|
982
|
+
class ExecutionRoleResource:
|
983
|
+
def __init__(self, execution_role_name: str = "PrefectEcsTaskExecutionRole"):
|
984
|
+
self._iam_client = boto3.client("iam")
|
985
|
+
self._execution_role_name = execution_role_name
|
986
|
+
self._trust_policy_document = json.dumps(
|
987
|
+
{
|
988
|
+
"Version": "2012-10-17",
|
989
|
+
"Statement": [
|
990
|
+
{
|
991
|
+
"Effect": "Allow",
|
992
|
+
"Principal": {"Service": "ecs-tasks.amazonaws.com"},
|
993
|
+
"Action": "sts:AssumeRole",
|
994
|
+
}
|
995
|
+
],
|
996
|
+
}
|
997
|
+
)
|
998
|
+
self._requires_provisioning = None
|
999
|
+
|
1000
|
+
async def get_task_count(self):
|
1001
|
+
"""
|
1002
|
+
Returns the number of tasks that will be executed to provision this resource.
|
1003
|
+
|
1004
|
+
Returns:
|
1005
|
+
int: The number of tasks to be provisioned.
|
1006
|
+
"""
|
1007
|
+
return 1 if await self.requires_provisioning() else 0
|
1008
|
+
|
1009
|
+
async def requires_provisioning(self) -> bool:
|
1010
|
+
"""
|
1011
|
+
Check if this resource requires provisioning.
|
1012
|
+
|
1013
|
+
Returns:
|
1014
|
+
bool: True if provisioning is required, False otherwise.
|
1015
|
+
"""
|
1016
|
+
if self._requires_provisioning is None:
|
1017
|
+
try:
|
1018
|
+
await anyio.to_thread.run_sync(
|
1019
|
+
partial(
|
1020
|
+
self._iam_client.get_role, RoleName=self._execution_role_name
|
1021
|
+
)
|
1022
|
+
)
|
1023
|
+
self._requires_provisioning = False
|
1024
|
+
except self._iam_client.exceptions.NoSuchEntityException:
|
1025
|
+
self._requires_provisioning = True
|
1026
|
+
|
1027
|
+
return self._requires_provisioning
|
1028
|
+
|
1029
|
+
async def get_planned_actions(self) -> List[str]:
|
1030
|
+
"""
|
1031
|
+
Returns a description of the planned actions for provisioning this resource.
|
1032
|
+
|
1033
|
+
Returns:
|
1034
|
+
Optional[str]: A description of the planned actions for provisioning the resource,
|
1035
|
+
or None if provisioning is not required.
|
1036
|
+
"""
|
1037
|
+
if await self.requires_provisioning():
|
1038
|
+
return [
|
1039
|
+
"Creating an IAM role assigned to ECS tasks:"
|
1040
|
+
f" [blue]{self._execution_role_name}[/]"
|
1041
|
+
]
|
1042
|
+
return []
|
1043
|
+
|
1044
|
+
async def provision(
|
1045
|
+
self,
|
1046
|
+
base_job_template: Dict[str, Any],
|
1047
|
+
advance: Callable[[], None],
|
1048
|
+
):
|
1049
|
+
"""
|
1050
|
+
Provisions an IAM role.
|
1051
|
+
|
1052
|
+
Args:
|
1053
|
+
base_job_template: The base job template of the work pool to provision
|
1054
|
+
infrastructure for.
|
1055
|
+
advance: A callback function to indicate progress.
|
1056
|
+
"""
|
1057
|
+
if await self.requires_provisioning():
|
1058
|
+
console = current_console.get()
|
1059
|
+
console.print("Provisioning execution role")
|
1060
|
+
response = await anyio.to_thread.run_sync(
|
1061
|
+
partial(
|
1062
|
+
self._iam_client.create_role,
|
1063
|
+
RoleName=self._execution_role_name,
|
1064
|
+
Description="Role for ECS tasks to access ECR and other resources.",
|
1065
|
+
AssumeRolePolicyDocument=self._trust_policy_document,
|
1066
|
+
)
|
1067
|
+
)
|
1068
|
+
await anyio.to_thread.run_sync(
|
1069
|
+
partial(
|
1070
|
+
self._iam_client.attach_role_policy,
|
1071
|
+
RoleName=self._execution_role_name,
|
1072
|
+
PolicyArn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
1073
|
+
)
|
1074
|
+
)
|
1075
|
+
advance()
|
1076
|
+
else:
|
1077
|
+
response = await anyio.to_thread.run_sync(
|
1078
|
+
partial(self._iam_client.get_role, RoleName=self._execution_role_name)
|
1079
|
+
)
|
1080
|
+
|
1081
|
+
base_job_template["variables"]["properties"]["execution_role_arn"][
|
1082
|
+
"default"
|
1083
|
+
] = response["Role"]["Arn"]
|
1084
|
+
return response["Role"]["Arn"]
|
1085
|
+
|
1086
|
+
@property
|
1087
|
+
def next_steps(self):
|
1088
|
+
return []
|
1089
|
+
|
765
1090
|
|
766
1091
|
class ElasticContainerServicePushProvisioner:
|
767
1092
|
"""
|
@@ -797,11 +1122,33 @@ class ElasticContainerServicePushProvisioner:
|
|
797
1122
|
except ModuleNotFoundError:
|
798
1123
|
return False
|
799
1124
|
|
800
|
-
def _generate_resources(
|
1125
|
+
def _generate_resources(
|
1126
|
+
self,
|
1127
|
+
work_pool_name: str,
|
1128
|
+
user_name: str = "prefect-ecs-user",
|
1129
|
+
policy_name: str = "prefect-ecs-policy",
|
1130
|
+
credentials_block_name: str = None,
|
1131
|
+
cluster_name: str = "prefect-ecs-cluster",
|
1132
|
+
vpc_name: str = "prefect-ecs-vpc",
|
1133
|
+
ecs_security_group_name: str = "prefect-ecs-security-group",
|
1134
|
+
repository_name: str = "prefect-flows",
|
1135
|
+
):
|
801
1136
|
return [
|
802
|
-
AuthenticationResource(
|
803
|
-
|
804
|
-
|
1137
|
+
AuthenticationResource(
|
1138
|
+
work_pool_name=work_pool_name,
|
1139
|
+
user_name=user_name,
|
1140
|
+
policy_name=policy_name,
|
1141
|
+
credentials_block_name=credentials_block_name,
|
1142
|
+
),
|
1143
|
+
ClusterResource(cluster_name=cluster_name),
|
1144
|
+
VpcResource(
|
1145
|
+
vpc_name=vpc_name,
|
1146
|
+
ecs_security_group_name=ecs_security_group_name,
|
1147
|
+
),
|
1148
|
+
ContainerRepositoryResource(
|
1149
|
+
work_pool_name=work_pool_name,
|
1150
|
+
repository_name=repository_name,
|
1151
|
+
),
|
805
1152
|
]
|
806
1153
|
|
807
1154
|
async def provision(
|
@@ -833,7 +1180,88 @@ class ElasticContainerServicePushProvisioner:
|
|
833
1180
|
)
|
834
1181
|
|
835
1182
|
try:
|
836
|
-
|
1183
|
+
if self.console.is_interactive and Confirm.ask(
|
1184
|
+
"Would you like to customize the resource names for your"
|
1185
|
+
" infrastructure? This includes an IAM user, IAM policy, ECS cluster,"
|
1186
|
+
" VPC, ECS security group, and ECR repository."
|
1187
|
+
):
|
1188
|
+
user_name = prompt(
|
1189
|
+
"Enter a name for the IAM user (manages ECS tasks)",
|
1190
|
+
default="prefect-ecs-user",
|
1191
|
+
)
|
1192
|
+
policy_name = prompt(
|
1193
|
+
(
|
1194
|
+
"Enter a name for the IAM policy (defines ECS task execution"
|
1195
|
+
" and image management permissions)"
|
1196
|
+
),
|
1197
|
+
default="prefect-ecs-policy",
|
1198
|
+
)
|
1199
|
+
cluster_name = prompt(
|
1200
|
+
"Enter a name for the ECS cluster (hosts ECS tasks)",
|
1201
|
+
default="prefect-ecs-cluster",
|
1202
|
+
)
|
1203
|
+
credentials_name = prompt(
|
1204
|
+
(
|
1205
|
+
"Enter a name for the AWS credentials block (stores AWS"
|
1206
|
+
" credentials for managing ECS tasks)"
|
1207
|
+
),
|
1208
|
+
default=f"{work_pool_name}-aws-credentials",
|
1209
|
+
)
|
1210
|
+
vpc_name = prompt(
|
1211
|
+
(
|
1212
|
+
"Enter a name for the VPC (provides network isolation for ECS"
|
1213
|
+
" tasks)"
|
1214
|
+
),
|
1215
|
+
default="prefect-ecs-vpc",
|
1216
|
+
)
|
1217
|
+
ecs_security_group_name = prompt(
|
1218
|
+
(
|
1219
|
+
"Enter a name for the ECS security group (controls task network"
|
1220
|
+
" traffic)"
|
1221
|
+
),
|
1222
|
+
default="prefect-ecs-security-group",
|
1223
|
+
)
|
1224
|
+
repository_name = prompt(
|
1225
|
+
(
|
1226
|
+
"Enter a name for the ECR repository (stores Docker images for"
|
1227
|
+
" ECS tasks)"
|
1228
|
+
),
|
1229
|
+
default="prefect-flows",
|
1230
|
+
)
|
1231
|
+
|
1232
|
+
provision_preview = Panel(
|
1233
|
+
dedent(
|
1234
|
+
f"""\
|
1235
|
+
Custom names for infrastructure resources for
|
1236
|
+
[blue]{work_pool_name}[/]:
|
1237
|
+
|
1238
|
+
- IAM user: [blue]{user_name}[/]
|
1239
|
+
- IAM policy: [blue]{policy_name}[/]
|
1240
|
+
- ECS cluster: [blue]{cluster_name}[/]
|
1241
|
+
- AWS credentials block: [blue]{credentials_name}[/]
|
1242
|
+
- VPC: [blue]{vpc_name}[/]
|
1243
|
+
- ECS security group: [blue]{ecs_security_group_name}[/]
|
1244
|
+
- ECR repository: [blue]{repository_name}[/]
|
1245
|
+
"""
|
1246
|
+
),
|
1247
|
+
expand=False,
|
1248
|
+
)
|
1249
|
+
|
1250
|
+
self.console.print(provision_preview)
|
1251
|
+
|
1252
|
+
resources = self._generate_resources(
|
1253
|
+
work_pool_name=work_pool_name,
|
1254
|
+
user_name=user_name,
|
1255
|
+
policy_name=policy_name,
|
1256
|
+
credentials_block_name=credentials_name,
|
1257
|
+
cluster_name=cluster_name,
|
1258
|
+
vpc_name=vpc_name,
|
1259
|
+
ecs_security_group_name=ecs_security_group_name,
|
1260
|
+
repository_name=repository_name,
|
1261
|
+
)
|
1262
|
+
|
1263
|
+
else:
|
1264
|
+
resources = self._generate_resources(work_pool_name=work_pool_name)
|
837
1265
|
|
838
1266
|
with Progress(
|
839
1267
|
SpinnerColumn(),
|
@@ -879,6 +1307,7 @@ class ElasticContainerServicePushProvisioner:
|
|
879
1307
|
# provision calls will be no-ops, but update the base job template
|
880
1308
|
|
881
1309
|
base_job_template_copy = deepcopy(base_job_template)
|
1310
|
+
next_steps = []
|
882
1311
|
with Progress(console=self._console, disable=num_tasks == 0) as progress:
|
883
1312
|
task = progress.add_task(
|
884
1313
|
"Provisioning Infrastructure",
|
@@ -890,6 +1319,12 @@ class ElasticContainerServicePushProvisioner:
|
|
890
1319
|
advance=partial(progress.advance, task),
|
891
1320
|
base_job_template=base_job_template_copy,
|
892
1321
|
)
|
1322
|
+
next_steps.append(resource.next_steps)
|
1323
|
+
|
1324
|
+
if next_steps:
|
1325
|
+
for step in next_steps:
|
1326
|
+
for item in step:
|
1327
|
+
self._console.print(item)
|
893
1328
|
|
894
1329
|
if num_tasks > 0:
|
895
1330
|
self._console.print(
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from .actions import create_flow_run_input, delete_flow_run_input, read_flow_run_input
|
2
|
+
from .run_input import RunInput, keyset_from_base_key, keyset_from_paused_state
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"RunInput",
|
6
|
+
"create_flow_run_input",
|
7
|
+
"keyset_from_base_key",
|
8
|
+
"keyset_from_paused_state",
|
9
|
+
"delete_flow_run_input",
|
10
|
+
"read_flow_run_input",
|
11
|
+
]
|