metaflow 2.15.18__py2.py3-none-any.whl → 2.15.20__py2.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.
Files changed (34) hide show
  1. metaflow/_vendor/imghdr/__init__.py +180 -0
  2. metaflow/cmd/develop/stub_generator.py +19 -2
  3. metaflow/plugins/__init__.py +3 -0
  4. metaflow/plugins/airflow/airflow.py +6 -0
  5. metaflow/plugins/argo/argo_workflows.py +316 -287
  6. metaflow/plugins/argo/exit_hooks.py +209 -0
  7. metaflow/plugins/aws/aws_utils.py +1 -1
  8. metaflow/plugins/aws/step_functions/step_functions.py +6 -0
  9. metaflow/plugins/cards/card_cli.py +20 -1
  10. metaflow/plugins/cards/card_creator.py +24 -1
  11. metaflow/plugins/cards/card_decorator.py +57 -1
  12. metaflow/plugins/cards/card_modules/convert_to_native_type.py +5 -2
  13. metaflow/plugins/cards/card_modules/test_cards.py +16 -0
  14. metaflow/plugins/cards/metadata.py +22 -0
  15. metaflow/plugins/exit_hook/__init__.py +0 -0
  16. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  17. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  18. metaflow/plugins/secrets/__init__.py +3 -0
  19. metaflow/plugins/secrets/secrets_decorator.py +9 -173
  20. metaflow/plugins/secrets/secrets_func.py +49 -0
  21. metaflow/plugins/secrets/secrets_spec.py +101 -0
  22. metaflow/plugins/secrets/utils.py +74 -0
  23. metaflow/runner/metaflow_runner.py +16 -1
  24. metaflow/runtime.py +45 -0
  25. metaflow/version.py +1 -1
  26. {metaflow-2.15.18.data → metaflow-2.15.20.data}/data/share/metaflow/devtools/Tiltfile +27 -2
  27. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/METADATA +2 -2
  28. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/RECORD +34 -25
  29. {metaflow-2.15.18.data → metaflow-2.15.20.data}/data/share/metaflow/devtools/Makefile +0 -0
  30. {metaflow-2.15.18.data → metaflow-2.15.20.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  31. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/WHEEL +0 -0
  32. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/entry_points.txt +0 -0
  33. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/licenses/LICENSE +0 -0
  34. {metaflow-2.15.18.dist-info → metaflow-2.15.20.dist-info}/top_level.txt +0 -0
@@ -1,183 +1,17 @@
1
1
  import os
2
- import re
3
2
 
4
3
  from metaflow.exception import MetaflowException
5
4
  from metaflow.decorators import StepDecorator
6
5
  from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
6
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
7
+ from metaflow.plugins.secrets.utils import (
8
+ get_secrets_backend_provider,
9
+ validate_env_vars,
10
+ validate_env_vars_across_secrets,
11
+ validate_env_vars_vs_existing_env,
12
+ )
7
13
  from metaflow.unbounded_foreach import UBF_TASK
8
14
 
9
- from typing import Any, Dict, List, Union
10
-
11
- DISALLOWED_SECRETS_ENV_VAR_PREFIXES = ["METAFLOW_"]
12
-
13
-
14
- def get_default_secrets_backend_type():
15
- from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
16
-
17
- if DEFAULT_SECRETS_BACKEND_TYPE is None:
18
- raise MetaflowException(
19
- "No default secrets backend type configured, but needed by @secrets. "
20
- "Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
21
- )
22
- return DEFAULT_SECRETS_BACKEND_TYPE
23
-
24
-
25
- class SecretSpec:
26
- def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
27
- self._secrets_backend_type = secrets_backend_type
28
- self._secret_id = secret_id
29
- self._options = options
30
- self._role = role
31
-
32
- @property
33
- def secrets_backend_type(self):
34
- return self._secrets_backend_type
35
-
36
- @property
37
- def secret_id(self):
38
- return self._secret_id
39
-
40
- @property
41
- def options(self):
42
- return self._options
43
-
44
- @property
45
- def role(self):
46
- return self._role
47
-
48
- def to_json(self):
49
- """Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
50
- return {
51
- "secrets_backend_type": self.secrets_backend_type,
52
- "secret_id": self.secret_id,
53
- "options": self.options,
54
- "role": self.role,
55
- }
56
-
57
- def __str__(self):
58
- return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
59
-
60
- @staticmethod
61
- def secret_spec_from_str(secret_spec_str, role):
62
- # "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
63
- # non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
64
- # Secrets Manager secrets.
65
- #
66
- # So we skew heavily optimize for best upfront UX for the present (1/2023).
67
- #
68
- # If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
69
- # At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
70
- # Other options could be:
71
- # - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
72
- # - add a Metaflow config variable that specifies the separator (default ".")
73
- # - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
74
- # + secret_id "XYZ" in aws-secrets-manager backend, OR
75
- # + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
76
- # + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
77
- parts = secret_spec_str.split(".", maxsplit=1)
78
- if len(parts) == 1:
79
- secrets_backend_type = get_default_secrets_backend_type()
80
- secret_id = parts[0]
81
- else:
82
- secrets_backend_type = parts[0]
83
- secret_id = parts[1]
84
- return SecretSpec(
85
- secrets_backend_type, secret_id=secret_id, options={}, role=role
86
- )
87
-
88
- @staticmethod
89
- def secret_spec_from_dict(secret_spec_dict, role):
90
- if "type" not in secret_spec_dict:
91
- secrets_backend_type = get_default_secrets_backend_type()
92
- else:
93
- secrets_backend_type = secret_spec_dict["type"]
94
- if not isinstance(secrets_backend_type, str):
95
- raise MetaflowException(
96
- "Bad @secrets specification - 'type' must be a string - found %s"
97
- % type(secrets_backend_type)
98
- )
99
- secret_id = secret_spec_dict.get("id")
100
- if not isinstance(secret_id, str):
101
- raise MetaflowException(
102
- "Bad @secrets specification - 'id' must be a string - found %s"
103
- % type(secret_id)
104
- )
105
- options = secret_spec_dict.get("options", {})
106
- if not isinstance(options, dict):
107
- raise MetaflowException(
108
- "Bad @secrets specification - 'option' must be a dict - found %s"
109
- % type(options)
110
- )
111
- role_for_source = secret_spec_dict.get("role", None)
112
- if role_for_source is not None:
113
- if not isinstance(role_for_source, str):
114
- raise MetaflowException(
115
- "Bad @secrets specification - 'role' must be a str - found %s"
116
- % type(role_for_source)
117
- )
118
- role = role_for_source
119
- return SecretSpec(
120
- secrets_backend_type, secret_id=secret_id, options=options, role=role
121
- )
122
-
123
-
124
- def validate_env_vars_across_secrets(all_secrets_env_vars):
125
- vars_injected_by = {}
126
- for secret_spec, env_vars in all_secrets_env_vars:
127
- for k in env_vars:
128
- if k in vars_injected_by:
129
- raise MetaflowException(
130
- "Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
131
- % (secret_spec, k, vars_injected_by[k])
132
- )
133
- vars_injected_by[k] = secret_spec
134
-
135
-
136
- def validate_env_vars_vs_existing_env(all_secrets_env_vars):
137
- for secret_spec, env_vars in all_secrets_env_vars:
138
- for k in env_vars:
139
- if k in os.environ:
140
- raise MetaflowException(
141
- "Secret '%s' will inject '%s' as env var, but it already exists in env"
142
- % (secret_spec, k)
143
- )
144
-
145
-
146
- def validate_env_vars(env_vars):
147
- for k, v in env_vars.items():
148
- if not isinstance(k, str):
149
- raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
150
- if not isinstance(v, str):
151
- raise MetaflowException(
152
- "Found non string value %s (%s)" % (str(v), type(v))
153
- )
154
- if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
155
- raise MetaflowException("Found invalid env var name '%s'." % k)
156
- for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
157
- if k.startswith(disallowed_prefix):
158
- raise MetaflowException(
159
- "Found disallowed env var name '%s' (starts with '%s')."
160
- % (k, disallowed_prefix)
161
- )
162
-
163
-
164
- def get_secrets_backend_provider(secrets_backend_type):
165
- from metaflow.plugins import SECRETS_PROVIDERS
166
-
167
- try:
168
- provider_cls = [
169
- pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
170
- ][0]
171
- return provider_cls()
172
- except IndexError:
173
- raise MetaflowException(
174
- "Unknown secrets backend type %s (available types: %s)"
175
- % (
176
- secrets_backend_type,
177
- ", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
178
- )
179
- )
180
-
181
15
 
182
16
  class SecretsDecorator(StepDecorator):
183
17
  """
@@ -188,6 +22,8 @@ class SecretsDecorator(StepDecorator):
188
22
  ----------
189
23
  sources : List[Union[str, Dict[str, Any]]], default: []
190
24
  List of secret specs, defining how the secrets are to be retrieved
25
+ role : str, optional, default: None
26
+ Role to use for fetching secrets
191
27
  """
192
28
 
193
29
  name = "secrets"
@@ -0,0 +1,49 @@
1
+ from typing import Any, Dict, Optional, Union
2
+
3
+ from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
4
+ from metaflow.exception import MetaflowException
5
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
6
+ from metaflow.plugins.secrets.utils import get_secrets_backend_provider
7
+
8
+
9
+ def get_secret(
10
+ source: Union[str, Dict[str, Any]], role: Optional[str] = None
11
+ ) -> Dict[str, str]:
12
+ """
13
+ Get secret from source
14
+
15
+ Parameters
16
+ ----------
17
+ source : Union[str, Dict[str, Any]]
18
+ Secret spec, defining how the secret is to be retrieved
19
+ role : str, optional
20
+ Role to use for fetching secrets
21
+ """
22
+ if role is None:
23
+ role = DEFAULT_SECRETS_ROLE
24
+
25
+ secret_spec = None
26
+
27
+ if isinstance(source, str):
28
+ secret_spec = SecretSpec.secret_spec_from_str(source, role=role)
29
+ elif isinstance(source, dict):
30
+ secret_spec = SecretSpec.secret_spec_from_dict(source, role=role)
31
+ else:
32
+ raise MetaflowException(
33
+ "get_secrets sources items must be either a string or a dict"
34
+ )
35
+
36
+ secrets_backend_provider = get_secrets_backend_provider(
37
+ secret_spec.secrets_backend_type
38
+ )
39
+ try:
40
+ dict_for_secret = secrets_backend_provider.get_secret_as_dict(
41
+ secret_spec.secret_id,
42
+ options=secret_spec.options,
43
+ role=secret_spec.role,
44
+ )
45
+ return dict_for_secret
46
+ except Exception as e:
47
+ raise MetaflowException(
48
+ "Failed to retrieve secret '%s': %s" % (secret_spec.secret_id, e)
49
+ )
@@ -0,0 +1,101 @@
1
+ from metaflow.exception import MetaflowException
2
+ from metaflow.plugins.secrets.utils import get_default_secrets_backend_type
3
+
4
+
5
+ class SecretSpec:
6
+ def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
7
+ self._secrets_backend_type = secrets_backend_type
8
+ self._secret_id = secret_id
9
+ self._options = options
10
+ self._role = role
11
+
12
+ @property
13
+ def secrets_backend_type(self):
14
+ return self._secrets_backend_type
15
+
16
+ @property
17
+ def secret_id(self):
18
+ return self._secret_id
19
+
20
+ @property
21
+ def options(self):
22
+ return self._options
23
+
24
+ @property
25
+ def role(self):
26
+ return self._role
27
+
28
+ def to_json(self):
29
+ """Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
30
+ return {
31
+ "secrets_backend_type": self.secrets_backend_type,
32
+ "secret_id": self.secret_id,
33
+ "options": self.options,
34
+ "role": self.role,
35
+ }
36
+
37
+ def __str__(self):
38
+ return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
39
+
40
+ @staticmethod
41
+ def secret_spec_from_str(secret_spec_str, role):
42
+ # "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
43
+ # non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
44
+ # Secrets Manager secrets.
45
+ #
46
+ # So we skew heavily optimize for best upfront UX for the present (1/2023).
47
+ #
48
+ # If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
49
+ # At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
50
+ # Other options could be:
51
+ # - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
52
+ # - add a Metaflow config variable that specifies the separator (default ".")
53
+ # - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
54
+ # + secret_id "XYZ" in aws-secrets-manager backend, OR
55
+ # + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
56
+ # + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
57
+ parts = secret_spec_str.split(".", maxsplit=1)
58
+ if len(parts) == 1:
59
+ secrets_backend_type = get_default_secrets_backend_type()
60
+ secret_id = parts[0]
61
+ else:
62
+ secrets_backend_type = parts[0]
63
+ secret_id = parts[1]
64
+ return SecretSpec(
65
+ secrets_backend_type, secret_id=secret_id, options={}, role=role
66
+ )
67
+
68
+ @staticmethod
69
+ def secret_spec_from_dict(secret_spec_dict, role):
70
+ if "type" not in secret_spec_dict:
71
+ secrets_backend_type = get_default_secrets_backend_type()
72
+ else:
73
+ secrets_backend_type = secret_spec_dict["type"]
74
+ if not isinstance(secrets_backend_type, str):
75
+ raise MetaflowException(
76
+ "Bad @secrets specification - 'type' must be a string - found %s"
77
+ % type(secrets_backend_type)
78
+ )
79
+ secret_id = secret_spec_dict.get("id")
80
+ if not isinstance(secret_id, str):
81
+ raise MetaflowException(
82
+ "Bad @secrets specification - 'id' must be a string - found %s"
83
+ % type(secret_id)
84
+ )
85
+ options = secret_spec_dict.get("options", {})
86
+ if not isinstance(options, dict):
87
+ raise MetaflowException(
88
+ "Bad @secrets specification - 'option' must be a dict - found %s"
89
+ % type(options)
90
+ )
91
+ role_for_source = secret_spec_dict.get("role", None)
92
+ if role_for_source is not None:
93
+ if not isinstance(role_for_source, str):
94
+ raise MetaflowException(
95
+ "Bad @secrets specification - 'role' must be a str - found %s"
96
+ % type(role_for_source)
97
+ )
98
+ role = role_for_source
99
+ return SecretSpec(
100
+ secrets_backend_type, secret_id=secret_id, options=options, role=role
101
+ )
@@ -0,0 +1,74 @@
1
+ import os
2
+ import re
3
+ from metaflow.exception import MetaflowException
4
+
5
+ DISALLOWED_SECRETS_ENV_VAR_PREFIXES = ["METAFLOW_"]
6
+
7
+
8
+ def get_default_secrets_backend_type():
9
+ from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
10
+
11
+ if DEFAULT_SECRETS_BACKEND_TYPE is None:
12
+ raise MetaflowException(
13
+ "No default secrets backend type configured, but needed by @secrets. "
14
+ "Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
15
+ )
16
+ return DEFAULT_SECRETS_BACKEND_TYPE
17
+
18
+
19
+ def validate_env_vars_across_secrets(all_secrets_env_vars):
20
+ vars_injected_by = {}
21
+ for secret_spec, env_vars in all_secrets_env_vars:
22
+ for k in env_vars:
23
+ if k in vars_injected_by:
24
+ raise MetaflowException(
25
+ "Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
26
+ % (secret_spec, k, vars_injected_by[k])
27
+ )
28
+ vars_injected_by[k] = secret_spec
29
+
30
+
31
+ def validate_env_vars_vs_existing_env(all_secrets_env_vars):
32
+ for secret_spec, env_vars in all_secrets_env_vars:
33
+ for k in env_vars:
34
+ if k in os.environ:
35
+ raise MetaflowException(
36
+ "Secret '%s' will inject '%s' as env var, but it already exists in env"
37
+ % (secret_spec, k)
38
+ )
39
+
40
+
41
+ def validate_env_vars(env_vars):
42
+ for k, v in env_vars.items():
43
+ if not isinstance(k, str):
44
+ raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
45
+ if not isinstance(v, str):
46
+ raise MetaflowException(
47
+ "Found non string value %s (%s)" % (str(v), type(v))
48
+ )
49
+ if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
50
+ raise MetaflowException("Found invalid env var name '%s'." % k)
51
+ for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
52
+ if k.startswith(disallowed_prefix):
53
+ raise MetaflowException(
54
+ "Found disallowed env var name '%s' (starts with '%s')."
55
+ % (k, disallowed_prefix)
56
+ )
57
+
58
+
59
+ def get_secrets_backend_provider(secrets_backend_type):
60
+ from metaflow.plugins import SECRETS_PROVIDERS
61
+
62
+ try:
63
+ provider_cls = [
64
+ pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
65
+ ][0]
66
+ return provider_cls()
67
+ except IndexError:
68
+ raise MetaflowException(
69
+ "Unknown secrets backend type %s (available types: %s)"
70
+ % (
71
+ secrets_backend_type,
72
+ ", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
73
+ )
74
+ )
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ import inspect
2
3
  import os
3
4
  import sys
4
5
  import json
@@ -200,8 +201,22 @@ class RunnerMeta(type):
200
201
  def f(self, *args, **kwargs):
201
202
  return runner_subcommand(self, *args, **kwargs)
202
203
 
203
- f.__doc__ = runner_subcommand.__doc__ or ""
204
+ f.__doc__ = runner_subcommand.__init__.__doc__ or ""
204
205
  f.__name__ = subcommand_name
206
+ sig = inspect.signature(runner_subcommand)
207
+ # We take all the same parameters except replace the first with
208
+ # simple "self"
209
+ new_parameters = {}
210
+ for name, param in sig.parameters.items():
211
+ if new_parameters:
212
+ new_parameters[name] = param
213
+ else:
214
+ new_parameters["self"] = inspect.Parameter(
215
+ "self", inspect.Parameter.POSITIONAL_OR_KEYWORD
216
+ )
217
+ f.__signature__ = inspect.Signature(
218
+ list(new_parameters.values()), return_annotation=runner_subcommand
219
+ )
205
220
 
206
221
  return f
207
222
 
metaflow/runtime.py CHANGED
@@ -569,6 +569,7 @@ class NativeRuntime(object):
569
569
  for step in self._flow:
570
570
  for deco in step.decorators:
571
571
  deco.runtime_finished(exception)
572
+ self._run_exit_hooks()
572
573
 
573
574
  # assert that end was executed and it was successful
574
575
  if ("end", ()) in self._finished:
@@ -591,6 +592,50 @@ class NativeRuntime(object):
591
592
  "The *end* step was not successful by the end of flow."
592
593
  )
593
594
 
595
+ def _run_exit_hooks(self):
596
+ try:
597
+ exit_hook_decos = self._flow._flow_decorators.get("exit_hook", [])
598
+ if not exit_hook_decos:
599
+ return
600
+
601
+ successful = ("end", ()) in self._finished or self._clone_only
602
+ pathspec = f"{self._graph.name}/{self._run_id}"
603
+ flow_file = self._environment.get_environment_info()["script"]
604
+
605
+ def _call(fn_name):
606
+ try:
607
+ result = (
608
+ subprocess.check_output(
609
+ args=[
610
+ sys.executable,
611
+ "-m",
612
+ "metaflow.plugins.exit_hook.exit_hook_script",
613
+ flow_file,
614
+ fn_name,
615
+ pathspec,
616
+ ],
617
+ env=os.environ,
618
+ )
619
+ .decode()
620
+ .strip()
621
+ )
622
+ print(result)
623
+ except subprocess.CalledProcessError as e:
624
+ print(f"[exit_hook] Hook '{fn_name}' failed with error: {e}")
625
+ except Exception as e:
626
+ print(f"[exit_hook] Unexpected error in hook '{fn_name}': {e}")
627
+
628
+ # Call all exit hook functions regardless of individual failures
629
+ for fn_name in [
630
+ name
631
+ for deco in exit_hook_decos
632
+ for name in (deco.success_hooks if successful else deco.error_hooks)
633
+ ]:
634
+ _call(fn_name)
635
+
636
+ except Exception as ex:
637
+ pass # do not fail due to exit hooks for whatever reason.
638
+
594
639
  def _killall(self):
595
640
  # If we are here, all children have received a signal and are shutting down.
596
641
  # We want to give them an opportunity to do so and then kill
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.15.18"
1
+ metaflow_version = "2.15.20"
@@ -224,6 +224,19 @@ if "argo-workflows" in enabled_components:
224
224
  ]
225
225
  )
226
226
 
227
+ # This fixes issue described in: https://github.com/argoproj/argo-workflows/issues/10340
228
+ k8s_yaml(encode_yaml({
229
+ 'apiVersion': 'v1',
230
+ 'kind': 'Secret',
231
+ 'metadata': {
232
+ 'name': 'default.service-account-token',
233
+ 'annotations': {
234
+ 'kubernetes.io/service-account.name': 'default'
235
+ }
236
+ },
237
+ 'type': 'kubernetes.io/service-account-token'
238
+ }))
239
+
227
240
  k8s_yaml(encode_yaml({
228
241
  'apiVersion': 'rbac.authorization.k8s.io/v1',
229
242
  'kind': 'Role',
@@ -231,11 +244,23 @@ if "argo-workflows" in enabled_components:
231
244
  'name': 'argo-workflowtaskresults-role',
232
245
  'namespace': 'default'
233
246
  },
234
- 'rules': [{
247
+ 'rules': [
248
+ {
235
249
  'apiGroups': ['argoproj.io'],
236
250
  'resources': ['workflowtaskresults'],
237
251
  'verbs': ['create', 'patch', 'get', 'list']
238
- }]
252
+ },
253
+ {
254
+ 'apiGroups': ['argoproj.io'],
255
+ 'resources': ['workflowtasksets'],
256
+ 'verbs': ['watch', 'list']
257
+ },
258
+ {
259
+ 'apiGroups': ['argoproj.io'],
260
+ 'resources': ['workflowtasksets/status'],
261
+ 'verbs': ['patch']
262
+ },
263
+ ]
239
264
  }))
240
265
 
241
266
  k8s_yaml(encode_yaml({
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaflow
3
- Version: 2.15.18
3
+ Version: 2.15.20
4
4
  Summary: Metaflow: More AI and ML, Less Engineering
5
5
  Author: Metaflow Developers
6
6
  Author-email: help@metaflow.org
@@ -26,7 +26,7 @@ License-File: LICENSE
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: boto3
28
28
  Provides-Extra: stubs
29
- Requires-Dist: metaflow-stubs==2.15.18; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.15.20; extra == "stubs"
30
30
  Dynamic: author
31
31
  Dynamic: author-email
32
32
  Dynamic: classifier