ob-metaflow-extensions 1.1.162rc0__tar.gz → 1.1.163__tar.gz
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.
Potentially problematic release.
This version of ob-metaflow-extensions might be problematic. Click here for more details.
- {ob-metaflow-extensions-1.1.162rc0/ob_metaflow_extensions.egg-info → ob_metaflow_extensions-1.1.163}/PKG-INFO +2 -2
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/__init__.py +27 -18
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/aws/__init__.py +4 -0
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/aws/assume_role.py +3 -0
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/aws/assume_role_decorator.py +65 -0
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/ollama/__init__.py +225 -0
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/ollama/ollama.py +1924 -0
- ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/ollama/status_card.py +292 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +23 -2
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163/ob_metaflow_extensions.egg-info}/PKG-INFO +1 -1
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/ob_metaflow_extensions.egg-info/SOURCES.txt +4 -2
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/setup.py +2 -2
- ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/plugins/fast_bakery/baker.py +0 -110
- ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/plugins/ollama/__init__.py +0 -89
- ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/plugins/ollama/ollama.py +0 -813
- ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/toplevel/ob_internal.py +0 -1
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/README.md +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/config/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/apps/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/apps/app_utils.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/apps/consts.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/apps/deploy_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/apps/supervisord_utils.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/auth_server.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/card_utilities/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/card_utilities/async_cards.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/card_utilities/extra_components.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/card_utilities/injector.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/coreweave.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/nebius.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/kubernetes/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/kubernetes/pod_killer.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nim/card.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nim/nim_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nim/nim_manager.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nim/utils.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/constants.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/exceptions.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/heartbeat_store.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_cli.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvcf/utils.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/exceptions.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/nvct.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/nvct_cli.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/nvct_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/nvct_runner.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/nvct/utils.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/ollama/constants.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/ollama/exceptions.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/perimeters.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/profilers/deco_injector.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/profilers/gpu_profile_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/secrets/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/secrets/secrets.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowflake/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/tensorboard/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/plugins/torchtune/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/profilers/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/profilers/gpu.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/remote_config.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/ollama/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/snowflake/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/metaflow_extensions/outerbounds/toplevel/plugins/torchtune/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/ob_metaflow_extensions.egg-info/dependency_links.txt +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/ob_metaflow_extensions.egg-info/requires.txt +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/ob_metaflow_extensions.egg-info/top_level.txt +0 -0
- {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163}/setup.cfg +0 -0
|
@@ -41,6 +41,9 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
41
41
|
import boto3
|
|
42
42
|
import botocore
|
|
43
43
|
from metaflow_extensions.outerbounds.plugins.auth_server import get_token
|
|
44
|
+
from metaflow_extensions.outerbounds.plugins.aws.assume_role import (
|
|
45
|
+
OBP_ASSUME_ROLE_ARN_ENV_VAR,
|
|
46
|
+
)
|
|
44
47
|
|
|
45
48
|
from hashlib import sha256
|
|
46
49
|
from metaflow.util import get_username
|
|
@@ -49,6 +52,10 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
49
52
|
|
|
50
53
|
token_info = get_token("/generate/aws")
|
|
51
54
|
|
|
55
|
+
# Check if the assume_role decorator has set a role ARN via environment variable
|
|
56
|
+
# This takes precedence over CSPR role
|
|
57
|
+
decorator_role_arn = os.environ.get(OBP_ASSUME_ROLE_ARN_ENV_VAR)
|
|
58
|
+
|
|
52
59
|
# Write token to a file. The file name is derived from the user name
|
|
53
60
|
# so it works with multiple users on the same machine.
|
|
54
61
|
#
|
|
@@ -69,12 +76,13 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
69
76
|
if token_info.get("cspr_role_arn"):
|
|
70
77
|
cspr_role = token_info["cspr_role_arn"]
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
# If assume_role decorator is used, prioritize it over CSPR role
|
|
80
|
+
effective_role = decorator_role_arn or cspr_role
|
|
81
|
+
if effective_role:
|
|
82
|
+
# If we have either a decorator role or CSPR role, set up AWS config
|
|
75
83
|
# with two profiles. One to get credentials for the task role
|
|
76
84
|
# in exchange for the OIDC token, and second to assume the
|
|
77
|
-
#
|
|
85
|
+
# effective role using the task role credentials.
|
|
78
86
|
import configparser
|
|
79
87
|
from io import StringIO
|
|
80
88
|
|
|
@@ -86,9 +94,9 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
86
94
|
"web_identity_token_file": token_file,
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
#
|
|
90
|
-
aws_config["profile
|
|
91
|
-
"role_arn":
|
|
97
|
+
# Effective role profile (decorator role or CSPR role)
|
|
98
|
+
aws_config["profile effective"] = {
|
|
99
|
+
"role_arn": effective_role,
|
|
92
100
|
"source_profile": "task",
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -104,7 +112,7 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
104
112
|
tmp_aws_config_file = f.name
|
|
105
113
|
os.rename(tmp_aws_config_file, aws_config_file)
|
|
106
114
|
os.environ["AWS_CONFIG_FILE"] = aws_config_file
|
|
107
|
-
os.environ["AWS_PROFILE"] = "
|
|
115
|
+
os.environ["AWS_PROFILE"] = "effective"
|
|
108
116
|
else:
|
|
109
117
|
os.environ["AWS_WEB_IDENTITY_TOKEN_FILE"] = token_file
|
|
110
118
|
os.environ["AWS_ROLE_ARN"] = token_info["role_arn"]
|
|
@@ -116,21 +124,22 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
116
124
|
if token_info.get("region"):
|
|
117
125
|
os.environ["AWS_DEFAULT_REGION"] = token_info["region"]
|
|
118
126
|
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
session = boto3.session.Session(profile_name="
|
|
127
|
+
if effective_role:
|
|
128
|
+
if role_arn == decorator_role_arn or role_arn == USE_CSPR_ROLE_ARN_IF_SET:
|
|
129
|
+
# We have either a decorator role or CSPR role, use the effective profile
|
|
130
|
+
# The generated AWS config will be used here since we set the
|
|
131
|
+
# AWS_CONFIG_FILE environment variable above.
|
|
132
|
+
session = boto3.session.Session(profile_name="effective")
|
|
125
133
|
else:
|
|
126
134
|
session = boto3.session.Session(profile_name="task")
|
|
127
135
|
else:
|
|
136
|
+
# No decorator role or CSPR role, use default session
|
|
128
137
|
# Not using AWS config, just AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN
|
|
129
138
|
session = boto3.session.Session()
|
|
130
139
|
|
|
131
|
-
if role_arn and role_arn != USE_CSPR_ROLE_ARN_IF_SET:
|
|
140
|
+
if role_arn and role_arn != USE_CSPR_ROLE_ARN_IF_SET and decorator_role_arn != None:
|
|
132
141
|
# If the user provided a role_arn, we assume that role
|
|
133
|
-
# using the task role credentials.
|
|
142
|
+
# using the task role credentials. This overrides everything else.
|
|
134
143
|
fetcher = botocore.credentials.AssumeRoleCredentialFetcher(
|
|
135
144
|
client_creator=session._session.create_client,
|
|
136
145
|
source_credentials=session._session.get_credentials(),
|
|
@@ -146,8 +155,8 @@ def get_boto3_session(role_arn=None, session_vars=None):
|
|
|
146
155
|
else:
|
|
147
156
|
# If the user didn't provide a role_arn, or if the role_arn
|
|
148
157
|
# is set to USE_CSPR_ROLE_ARN_IF_SET, we return the default
|
|
149
|
-
# session which would use the
|
|
150
|
-
#
|
|
158
|
+
# session which would use the effective role (decorator or CSPR) if set,
|
|
159
|
+
# or the task role otherwise.
|
|
151
160
|
return session
|
|
152
161
|
|
|
153
162
|
|
ob_metaflow_extensions-1.1.163/metaflow_extensions/outerbounds/plugins/aws/assume_role_decorator.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from metaflow.user_configs.config_decorators import (
|
|
2
|
+
MutableFlow,
|
|
3
|
+
MutableStep,
|
|
4
|
+
CustomFlowDecorator,
|
|
5
|
+
)
|
|
6
|
+
from .assume_role import OBP_ASSUME_ROLE_ARN_ENV_VAR
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class assume_role(CustomFlowDecorator):
|
|
10
|
+
"""
|
|
11
|
+
Flow-level decorator for assuming AWS IAM roles.
|
|
12
|
+
|
|
13
|
+
When applied to a flow, all steps in the flow will automatically use the specified IAM role-arn
|
|
14
|
+
as their source principal.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
------
|
|
18
|
+
@assume_role(role_arn="arn:aws:iam::123456789012:role/my-iam-role")
|
|
19
|
+
class MyFlow(FlowSpec):
|
|
20
|
+
@step
|
|
21
|
+
def start(self):
|
|
22
|
+
import boto3
|
|
23
|
+
client = boto3.client("dynamodb") # Automatically uses the role in the flow decorator
|
|
24
|
+
self.next(self.end)
|
|
25
|
+
|
|
26
|
+
@step
|
|
27
|
+
def end(self):
|
|
28
|
+
from metaflow import get_aws_client
|
|
29
|
+
client = get_aws_client("dynamodb") # Automatically uses the role in the flow decorator
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def init(self, *args, **kwargs):
|
|
33
|
+
self.role_arn = kwargs.get("role_arn", None)
|
|
34
|
+
|
|
35
|
+
if self.role_arn is None:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"`role_arn` keyword argument is required for the assume_role decorator"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not self.role_arn.startswith("arn:aws:iam::"):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"`role_arn` must be a valid AWS IAM role ARN starting with 'arn:aws:iam::'"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def evaluate(self, mutable_flow: MutableFlow) -> None:
|
|
46
|
+
"""
|
|
47
|
+
This method is called by Metaflow to apply the decorator to the flow.
|
|
48
|
+
It sets up environment variables that will be used by the AWS client
|
|
49
|
+
to automatically assume the specified role.
|
|
50
|
+
"""
|
|
51
|
+
# Import environment decorator at runtime to avoid circular imports
|
|
52
|
+
from metaflow import environment
|
|
53
|
+
|
|
54
|
+
# Set the role ARN as an environment variable that will be picked up
|
|
55
|
+
# by the get_aws_client function
|
|
56
|
+
def _setup_role_assumption(step: MutableStep) -> None:
|
|
57
|
+
# We'll inject the role assumption by adding an environment decorator
|
|
58
|
+
# The role will be available through an environment variable
|
|
59
|
+
step.add_decorator(
|
|
60
|
+
environment, vars={OBP_ASSUME_ROLE_ARN_ENV_VAR: self.role_arn}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Apply the role assumption setup to all steps in the flow
|
|
64
|
+
for _, step in mutable_flow.steps:
|
|
65
|
+
_setup_role_assumption(step)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from metaflow.decorators import StepDecorator
|
|
2
|
+
from metaflow import current
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from .ollama import OllamaManager, OllamaRequestInterceptor
|
|
8
|
+
from .status_card import OllamaStatusCard
|
|
9
|
+
from ..card_utilities.injector import CardDecoratorInjector
|
|
10
|
+
|
|
11
|
+
__mf_promote_submodules__ = ["plugins.ollama"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OllamaDecorator(StepDecorator, CardDecoratorInjector):
|
|
15
|
+
"""
|
|
16
|
+
This decorator is used to run Ollama APIs as Metaflow task sidecars.
|
|
17
|
+
|
|
18
|
+
User code call
|
|
19
|
+
--------------
|
|
20
|
+
@ollama(
|
|
21
|
+
models=[...],
|
|
22
|
+
...
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Valid backend options
|
|
26
|
+
---------------------
|
|
27
|
+
- 'local': Run as a separate process on the local task machine.
|
|
28
|
+
- (TODO) 'managed': Outerbounds hosts and selects compute provider.
|
|
29
|
+
- (TODO) 'remote': Spin up separate instance to serve Ollama models.
|
|
30
|
+
|
|
31
|
+
Valid model options
|
|
32
|
+
-------------------
|
|
33
|
+
Any model here https://ollama.com/search, e.g. 'llama3.2', 'llama3.3'
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
models: list[str]
|
|
38
|
+
List of Ollama containers running models in sidecars.
|
|
39
|
+
backend: str
|
|
40
|
+
Determines where and how to run the Ollama process.
|
|
41
|
+
force_pull: bool
|
|
42
|
+
Whether to run `ollama pull` no matter what, or first check the remote cache in Metaflow datastore for this model key.
|
|
43
|
+
cache_update_policy: str
|
|
44
|
+
Cache update policy: "auto", "force", or "never".
|
|
45
|
+
force_cache_update: bool
|
|
46
|
+
Simple override for "force" cache update policy.
|
|
47
|
+
debug: bool
|
|
48
|
+
Whether to turn on verbose debugging logs.
|
|
49
|
+
circuit_breaker_config: dict
|
|
50
|
+
Configuration for circuit breaker protection. Keys: failure_threshold, recovery_timeout, reset_timeout.
|
|
51
|
+
timeout_config: dict
|
|
52
|
+
Configuration for various operation timeouts. Keys: pull, stop, health_check, install, server_startup.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
name = "ollama"
|
|
56
|
+
defaults = {
|
|
57
|
+
"models": [],
|
|
58
|
+
"backend": "local",
|
|
59
|
+
"force_pull": False,
|
|
60
|
+
"cache_update_policy": "auto", # "auto", "force", "never"
|
|
61
|
+
"force_cache_update": False, # Simple override for "force"
|
|
62
|
+
"debug": False,
|
|
63
|
+
"circuit_breaker_config": {
|
|
64
|
+
"failure_threshold": 3,
|
|
65
|
+
"recovery_timeout": 60,
|
|
66
|
+
"reset_timeout": 30,
|
|
67
|
+
},
|
|
68
|
+
"timeout_config": {
|
|
69
|
+
"pull": 600, # 10 minutes for model pulls
|
|
70
|
+
"stop": 30, # 30 seconds for model stops
|
|
71
|
+
"health_check": 5, # 5 seconds for health checks
|
|
72
|
+
"install": 60, # 1 minute for Ollama installation
|
|
73
|
+
"server_startup": 300, # 5 minutes for server startup
|
|
74
|
+
},
|
|
75
|
+
"card_refresh_interval": 10, # seconds - how often to update the status card
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def step_init(
|
|
79
|
+
self, flow, graph, step_name, decorators, environment, flow_datastore, logger
|
|
80
|
+
):
|
|
81
|
+
super().step_init(
|
|
82
|
+
flow, graph, step_name, decorators, environment, flow_datastore, logger
|
|
83
|
+
)
|
|
84
|
+
self.flow_datastore_backend = flow_datastore._storage_impl
|
|
85
|
+
|
|
86
|
+
# Attach the ollama status card
|
|
87
|
+
self.attach_card_decorator(
|
|
88
|
+
flow,
|
|
89
|
+
step_name,
|
|
90
|
+
"ollama_status",
|
|
91
|
+
"blank",
|
|
92
|
+
refresh_interval=self.attributes["card_refresh_interval"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def task_decorate(
|
|
96
|
+
self, step_func, flow, graph, retry_count, max_user_code_retries, ubf_context
|
|
97
|
+
):
|
|
98
|
+
@functools.wraps(step_func)
|
|
99
|
+
def ollama_wrapper():
|
|
100
|
+
self.ollama_manager = None
|
|
101
|
+
self.request_interceptor = None
|
|
102
|
+
self.status_card = None
|
|
103
|
+
self.card_monitor_thread = None
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# Initialize status card and monitoring
|
|
107
|
+
self.status_card = OllamaStatusCard(
|
|
108
|
+
refresh_interval=self.attributes["card_refresh_interval"]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Start card monitoring in background
|
|
112
|
+
def monitor_card():
|
|
113
|
+
try:
|
|
114
|
+
self.status_card.on_startup(current.card["ollama_status"])
|
|
115
|
+
|
|
116
|
+
while not getattr(
|
|
117
|
+
self.card_monitor_thread, "_stop_event", False
|
|
118
|
+
):
|
|
119
|
+
try:
|
|
120
|
+
# Trigger card update with current data
|
|
121
|
+
self.status_card.on_update(
|
|
122
|
+
current.card["ollama_status"], None
|
|
123
|
+
)
|
|
124
|
+
import time
|
|
125
|
+
|
|
126
|
+
time.sleep(self.attributes["card_refresh_interval"])
|
|
127
|
+
except Exception as e:
|
|
128
|
+
if self.attributes["debug"]:
|
|
129
|
+
print(f"[@ollama] Card monitoring error: {e}")
|
|
130
|
+
break
|
|
131
|
+
except Exception as e:
|
|
132
|
+
if self.attributes["debug"]:
|
|
133
|
+
print(f"[@ollama] Card monitor thread error: {e}")
|
|
134
|
+
self.status_card.on_error(current.card["ollama_status"], str(e))
|
|
135
|
+
|
|
136
|
+
self.card_monitor_thread = threading.Thread(
|
|
137
|
+
target=monitor_card, daemon=True
|
|
138
|
+
)
|
|
139
|
+
self.card_monitor_thread._stop_event = False
|
|
140
|
+
self.card_monitor_thread.start()
|
|
141
|
+
|
|
142
|
+
# Initialize OllamaManager with status card
|
|
143
|
+
self.ollama_manager = OllamaManager(
|
|
144
|
+
models=self.attributes["models"],
|
|
145
|
+
backend=self.attributes["backend"],
|
|
146
|
+
flow_datastore_backend=self.flow_datastore_backend,
|
|
147
|
+
force_pull=self.attributes["force_pull"],
|
|
148
|
+
cache_update_policy=self.attributes["cache_update_policy"],
|
|
149
|
+
force_cache_update=self.attributes["force_cache_update"],
|
|
150
|
+
debug=self.attributes["debug"],
|
|
151
|
+
circuit_breaker_config=self.attributes["circuit_breaker_config"],
|
|
152
|
+
timeout_config=self.attributes["timeout_config"],
|
|
153
|
+
status_card=self.status_card,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Install request protection by monkey-patching ollama package
|
|
157
|
+
self.request_interceptor = OllamaRequestInterceptor(
|
|
158
|
+
self.ollama_manager.circuit_breaker, self.attributes["debug"]
|
|
159
|
+
)
|
|
160
|
+
self.request_interceptor.install_protection()
|
|
161
|
+
|
|
162
|
+
if self.attributes["debug"]:
|
|
163
|
+
print(
|
|
164
|
+
"[@ollama] OllamaManager initialized and request protection installed"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
if self.status_card:
|
|
169
|
+
self.status_card.add_event(
|
|
170
|
+
"error", f"Initialization failed: {str(e)}"
|
|
171
|
+
)
|
|
172
|
+
try:
|
|
173
|
+
self.status_card.on_error(current.card["ollama_status"], str(e))
|
|
174
|
+
except:
|
|
175
|
+
pass
|
|
176
|
+
print(f"[@ollama] Error initializing OllamaManager: {e}")
|
|
177
|
+
raise
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
if self.status_card:
|
|
181
|
+
self.status_card.add_event("info", "Starting user step function")
|
|
182
|
+
step_func()
|
|
183
|
+
if self.status_card:
|
|
184
|
+
self.status_card.add_event(
|
|
185
|
+
"success", "User step function completed successfully"
|
|
186
|
+
)
|
|
187
|
+
finally:
|
|
188
|
+
# Remove request protection first (before terminating models)
|
|
189
|
+
if self.request_interceptor:
|
|
190
|
+
self.request_interceptor.remove_protection()
|
|
191
|
+
if self.attributes["debug"]:
|
|
192
|
+
print("[@ollama] Request protection removed")
|
|
193
|
+
|
|
194
|
+
# Then cleanup ollama manager (while card monitoring is still active)
|
|
195
|
+
if self.ollama_manager:
|
|
196
|
+
self.ollama_manager.terminate_models()
|
|
197
|
+
|
|
198
|
+
# Give the card a moment to render the final shutdown events
|
|
199
|
+
if self.card_monitor_thread and self.status_card:
|
|
200
|
+
import time
|
|
201
|
+
|
|
202
|
+
# Trigger one final card update to capture all shutdown events
|
|
203
|
+
try:
|
|
204
|
+
self.status_card.on_update(current.card["ollama_status"], None)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
if self.attributes["debug"]:
|
|
207
|
+
print(f"[@ollama] Final card update error: {e}")
|
|
208
|
+
time.sleep(2) # Allow final events to be rendered
|
|
209
|
+
|
|
210
|
+
# Now stop card monitoring
|
|
211
|
+
if self.card_monitor_thread:
|
|
212
|
+
self.card_monitor_thread._stop_event = True
|
|
213
|
+
|
|
214
|
+
if self.ollama_manager and self.attributes["debug"]:
|
|
215
|
+
print(
|
|
216
|
+
f"[@ollama] process statuses: {self.ollama_manager.processes}"
|
|
217
|
+
)
|
|
218
|
+
print(
|
|
219
|
+
f"[@ollama] process runtime stats: {self.ollama_manager.stats}"
|
|
220
|
+
)
|
|
221
|
+
print(
|
|
222
|
+
f"[@ollama] Circuit Breaker status: {self.ollama_manager.circuit_breaker.get_status()}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return ollama_wrapper
|