Skip to content

Commit b8fe273

Browse files
jiaenrenclaude
andauthored
Add workflow label CLI support (#1225)
* Add workflow label CLI support Add repeatable --label KEY=VALUE flags to workflow submit, workflow validate, and app submit (flags override the YAML per run); --label selector and --no-label filters on workflow list; a Labels column in list output and a Labels line in workflow query; and WARNING lines for warn-mode policy messages on submit and validate responses. Document the spec field, submit flags, list filters, and the labels_config admin reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Apply CLI review simplifications Simplify the Labels column fallback, drop two low-value argparse tests, remove a dead spelling-wordlist entry, and move the osmo_tasks_count label-dimension reference docs to the metrics PR that implements them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Drop the workflow-label reserved-key deny-list Any syntactically valid Kubernetes label key is accepted, including system domains such as app.kubernetes.io/name, which the deny-list wrongly blocked. Protection against user labels actuating scheduling or platform machinery comes from merge order at stamping time: system labels are applied after the user map on every path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Polish CLI help texts and label documentation Align argparse continuation style with sibling flags, give the list filter help a subject and generic key examples, add missing test class docstrings, restructure the admin policy table to the file's standard format, quote 'off' in the rollback guidance, title-case headings, and replace design-register phrasing in the user guide with the actual warning output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 069ac4e commit b8fe273

9 files changed

Lines changed: 489 additions & 10 deletions

File tree

docs/deployment_guide/references/configs_definitions/workflow.rst

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Top-Level Configuration
7070
- `Plugins`_
7171
- Configuration for workflow plugins.
7272
- See Plugins section
73+
* - ``labels_config``
74+
- `Workflow Labels`_
75+
- Workflow-label policies, accepted values, and staged enforcement.
76+
- ``policy: []``
7377
* - ``max_num_tasks``
7478
- Integer
7579
- Maximum number of tasks allowed in a workflow.
@@ -240,6 +244,80 @@ Workflow Information
240244
- Maximum allowed length for workflow names.
241245
- ``64``
242246

247+
Workflow Labels
248+
===============
249+
250+
Workflow labels are optional and format-checked even when no label is required.
251+
The default configuration applies no label policies:
252+
253+
.. code-block:: yaml
254+
255+
labels_config:
256+
policy: []
257+
258+
Each entry in ``policy`` controls one key independently. Use ``off`` or omit a
259+
key from ``policy`` to disable both warnings and enforcement for that key:
260+
261+
.. code-block:: yaml
262+
263+
labels_config:
264+
policy:
265+
- key: team
266+
allow_list:
267+
- robotics
268+
- simulation
269+
enforcement: warn
270+
- key: cost-center
271+
allow_list: []
272+
enforcement: enforce
273+
274+
.. list-table::
275+
:header-rows: 1
276+
:widths: 25 12 43 20
277+
278+
* - **Field**
279+
- **Type**
280+
- **Description**
281+
- **Default Values**
282+
* - ``key``
283+
- String
284+
- Kubernetes label key to check. Duplicate policy keys are rejected,
285+
and at most 16 keys can be configured.
286+
- Required
287+
* - ``allow_list``
288+
- List of Strings
289+
- Exact accepted values. An empty list accepts any well-formed value.
290+
- ``[]``
291+
* - ``enforcement``
292+
- String (``"off"``, ``warn``, ``enforce``)
293+
- ``off`` accepts without policy warnings. ``warn`` accepts but warns
294+
when the key is missing or its value is outside a non-empty allow-list.
295+
``enforce`` rejects those violations.
296+
- ``"off"``
297+
298+
The same policy applies to new submissions, resubmission by ID, restart, and
299+
validation-only requests. An ``enforcement: enforce`` rejection creates
300+
neither a workflow row nor a stored specification. Submit responses carry
301+
warnings from that admission check. Warnings are not stored with the
302+
workflow: detail responses recompute warn-mode violations from the stored
303+
labels and the current configuration, so displayed warnings track policy
304+
changes even for completed workflows.
305+
306+
To roll back enforcement immediately, use ``enforcement: warn``. To disable both
307+
warnings and enforcement, use ``enforcement: "off"`` (quoted: unquoted YAML
308+
``off`` parses as boolean false) or remove the policy entry.
309+
Existing and in-flight workflows are not modified, although their detail-page
310+
warnings always reflect the current warn policy. In ConfigMap mode, an invalid
311+
edit is rejected and the previous valid snapshot remains active.
312+
313+
Admission emits
314+
``osmo_label_validation_total{key, outcome}``, where ``outcome`` is ``ok``,
315+
``missing``, ``invalid``, or ``rejected``. The counter covers rejected
316+
submissions that do not create a workflow row. Keep the policy list small to
317+
control metric cardinality. Exporting a Pod label through ``kube_pod_labels`` is a
318+
separate kube-state-metrics allow-list decision; see
319+
:ref:`adding_observability`.
320+
243321
Backend Images
244322
==============
245323

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ resrcs
266266
restitutions
267267
retryable
268268
reusability
269+
rollout
269270
ros
270271
routable
271272
rpc

docs/user_guide/workflows/specification/index.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ Workflow
4242
- ``string``
4343
- No
4444
- The pool to submit the workflow to.
45+
* - :kbd:`labels`
46+
- ``dict``
47+
- No
48+
- Immutable workflow metadata copied to every task pod. See
49+
:ref:`workflow_spec_labels`.
4550
* - :kbd:`timeout`
4651
- ``dict``
4752
- No
@@ -59,6 +64,37 @@ Workflow
5964
- **Yes** (or :kbd:`tasks`)
6065
- List of :ref:`group definitions <workflow_spec_group>`.
6166

67+
.. _workflow_spec_labels:
68+
69+
Workflow Labels
70+
---------------
71+
72+
Labels identify a workflow independently of mutable tags. Keys and values use
73+
Kubernetes label syntax, values must be non-empty, and a workflow can define
74+
at most 16 labels. Labels are stored with the submitted specification and
75+
copied only to task pods, not to Services, Secrets, scheduler groups, or
76+
other objects. Any syntactically valid key is accepted; where a workflow
77+
label collides with a system-owned pod label (the ``osmo.`` selectors or
78+
scheduler queue labels), the system value wins on the pod.
79+
80+
.. code-block:: yaml
81+
82+
workflow:
83+
name: training
84+
labels:
85+
team: robotics
86+
experiment: run42
87+
tasks:
88+
- name: train
89+
image: ubuntu:24.04
90+
command: [bash]
91+
args: [-lc, "echo training"]
92+
93+
Your administrator may configure particular keys in ``off``, ``warn``, or
94+
``enforce`` mode. A submission can succeed and still print a warning while an
95+
administrator is rolling out a requirement. Use :ref:`workflow_submission` to
96+
validate and override labels without editing a shared specification.
97+
6298
.. _workflow_spec_task:
6399

64100
Task

docs/user_guide/workflows/submission.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,63 @@ Override workflow values at submission time:
179179
--set-string model_name="bert-base" \
180180
--set-env WANDB_PROJECT=my-experiment
181181
182+
Set Workflow Labels
183+
~~~~~~~~~~~~~~~~~~~
184+
185+
Add or override immutable workflow labels with repeatable ``--label key=value``
186+
flags. A command-line value wins over the same key in the workflow YAML.
187+
188+
.. code-block:: bash
189+
190+
$ osmo workflow submit training.yaml \
191+
--label team=robotics \
192+
--label experiment=run42
193+
194+
The same flag works for app submission and resubmission by workflow ID
195+
(``osmo workflow submit <workflow-id>``). Restart does not accept label
196+
overrides because it reuses the stored specification; resubmit the workflow
197+
when a stored label must change.
198+
199+
Validate labels and any administrator policy without creating a workflow:
200+
201+
.. code-block:: bash
202+
203+
$ osmo workflow validate training.yaml --label team=robotics
204+
205+
If a label policy is in warn mode, the submission succeeds but prints a
206+
``WARNING:`` line explaining what to change before the policy is enforced,
207+
for example::
208+
209+
WARNING: Workflow is missing label 'team'; add it now to avoid rejected
210+
submissions once it is required.
211+
212+
In enforce mode the submission is rejected and the error identifies the
213+
missing key or disallowed value.
214+
215+
Find workflows with an exact label, a glob selector, an alternative selector,
216+
or a missing key:
217+
218+
.. code-block:: bash
219+
220+
$ osmo workflow list --label team=robotics --label experiment=run42
221+
$ osmo workflow list --label 'project=robotics_*'
222+
$ osmo workflow list --label 'project=(sim_*|hil_*)'
223+
$ osmo workflow list --label 'team=robotics_(a|b)'
224+
$ osmo workflow list --no-label team
225+
226+
Label selectors are case-sensitive. In a glob selector, ``*`` matches zero or
227+
more characters; every other character, including ``_``, is literal. A
228+
parenthesized ``|`` group can appear within a selector and matches any one of
229+
its alternatives. Alternatives can contain ``*`` wildcards. Groups are flat
230+
(not nested), each group must contain at least two non-empty alternatives,
231+
and the alternatives in a selector may multiply out to at most 32
232+
combinations. Quote pattern selectors so the shell does not interpret them.
233+
234+
Repeated ``--label`` filters are combined with AND, so every supplied
235+
selector must match. Alternatives within one selector are combined with OR. Pattern syntax
236+
applies only to workflow list filters; submission and validation labels must
237+
still contain exact Kubernetes label values.
238+
182239
Dry Run Validation
183240
~~~~~~~~~~~~~~~~~~
184241

src/cli/app.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@ def setup_parser(parser: argparse._SubParsersAction):
219219
'LOW. LOW workflows may be preempted to allow a '
220220
'higher priority workflow to run.',
221221
choices=[p.value for p in wf_priority.WorkflowPriority])
222+
submit_parser.add_argument('--label',
223+
action='append',
224+
dest='labels',
225+
default=[],
226+
metavar='KEY=VALUE',
227+
help='Set a workflow label. Repeat to set multiple labels. '
228+
'Values override labels declared by the app.')
222229
submit_parser.set_defaults(func=_submit_app)
223230

224231

@@ -511,6 +518,8 @@ def _submit_app(service_client: client.ServiceClient, args: argparse.Namespace):
511518

512519
if args.priority:
513520
params['priority'] = args.priority
521+
if args.labels:
522+
params['label'] = args.labels
514523

515524
template_data = workflow.parse_file_for_template(app_spec_result,
516525
args.set,

src/cli/tests/BUILD

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ py_test(
2828
]
2929
)
3030

31+
py_test(
32+
name = "test_workflow",
33+
srcs = [
34+
"test_workflow.py"
35+
],
36+
deps = [
37+
"//src/cli:cli_lib",
38+
]
39+
)
40+
3141
py_test(
3242
name = "test_config_update",
3343
srcs = [
@@ -87,13 +97,3 @@ py_test(
8797
"//src/cli:cli_lib",
8898
]
8999
)
90-
91-
py_test(
92-
name = "test_workflow",
93-
srcs = [
94-
"test_workflow.py"
95-
],
96-
deps = [
97-
"//src/cli:cli_lib",
98-
]
99-
)

src/cli/tests/test_app.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ def test_submit_command_set_options(self):
158158
self.assertEqual(args.set_string, ['s=foo'])
159159
self.assertEqual(args.set_env, ['K=V'])
160160

161+
def test_submit_command_labels(self):
162+
parser = self._build_parser()
163+
args = parser.parse_args([
164+
'app', 'submit', 'my-app',
165+
'--label', 'team=alpha', '--label', 'run=42',
166+
])
167+
168+
self.assertEqual(args.labels, ['team=alpha', 'run=42'])
169+
161170

162171
class TestCreateApp(unittest.TestCase):
163172
"""Test cases for _create_app."""
@@ -649,6 +658,7 @@ def _make_args(self, **overrides) -> argparse.Namespace:
649658
'local_path': '/some/path',
650659
'rsync': None,
651660
'format_type': 'text',
661+
'labels': [],
652662
}
653663
defaults.update(overrides)
654664
return argparse.Namespace(**defaults)
@@ -674,6 +684,22 @@ def test_submit_app_with_pool_and_priority(self):
674684
self.assertEqual(params['app_version'], 3)
675685
self.assertEqual(params['priority'], 'HIGH')
676686

687+
def test_submit_app_forwards_label_overrides(self):
688+
service_client = mock.Mock(spec=client.ServiceClient)
689+
service_client.request.side_effect = [
690+
{'uuid': 'uuid-1', 'versions': [{'version': 3}]},
691+
'spec-text',
692+
]
693+
args = self._make_args(labels=['team=alpha', 'run=42'])
694+
695+
with mock.patch('src.cli.app.workflow.parse_file_for_template',
696+
return_value='template-data'), \
697+
mock.patch('src.cli.app.workflow.submit_workflow_helper') as submit_mock:
698+
app._submit_app(service_client, args)
699+
700+
params = submit_mock.call_args.args[4]
701+
self.assertEqual(params['label'], ['team=alpha', 'run=42'])
702+
677703
def test_submit_app_no_pool_fetches_default(self):
678704
service_client = mock.Mock(spec=client.ServiceClient)
679705
service_client.request.side_effect = [

0 commit comments

Comments
 (0)