Repository: PrefectHQ/prefect

Issue:
Set flow labels through the CLI register command
## Current behavior
Right now there is no way to add a label to a **flow** when registering it using the CLI. You only can set the label from inside the flow's code.

## Proposed behavior
It would be really nice to have a `--label` parameter (just like the one we have for agents) that would allow us to add labels on the fly when registering a flow from the CLI.

## Example
Adding the parameter would look something like this:
`prefect register flow --file my_flow.py --name My-Flow --label my-label`
or this:
`prefect register flow --file my_flow.py --name My-Flow -l my-label`

This could be very useful for registering flows with continuous integration depending on the environment you want to build the flow for (for example, `dev` or `prod`).

Thanks a lot and keep it up!



Discussion:
Thanks for the feature request, this would be a welcome feature.

One question:

If a flow already configures labels (`['a']`) on it, what's the expected behavior with labels set via the CLI (`['b', 'c']`)?

- Override the labels set on the flow (`['b', 'c']`)
- Extend the labels set on the flow with those from the CLI (`['a', 'b', 'c']`)
- Error, can either set labels on the flow directly, or via the CLI, but not both

Right now I'm leaning towards extending the list of labels as the expected behavior.
That's a good point!

I guess I'm with you. Extending the list is the most intuitive, at least for me.

Resolving patch:
diff --git a/src/prefect/cli/register.py b/src/prefect/cli/register.py
--- a/src/prefect/cli/register.py
+++ b/src/prefect/cli/register.py
@@ -54,7 +54,14 @@ def register():
     hidden=True,
     default=None,
 )
-def flow(file, name, project):
+@click.option(
+    "--label",
+    "-l",
+    required=False,
+    hidden=True,
+    multiple=True,
+)
+def flow(file, name, project, label):
     """
     Register a flow from a file. This call will pull a Flow object out of a `.py` file
     and call `flow.register` on it.
@@ -64,16 +71,23 @@ def flow(file, name, project):
         --file, -f      TEXT    The path to a local file which contains a flow  [required]
         --name, -n      TEXT    The `flow.name` to pull out of the file provided. If a name
                                 is not provided then the first flow object found will be registered.
-        --project       TEXT    The name of a Prefect project to register this flow
+        --project, -p   TEXT    The name of a Prefect project to register this flow
+        --label, -l     TEXT    A label to set on the flow, extending any existing labels.
+                                Multiple labels are supported, eg. `-l label1 -l label2`.
 
     \b
     Examples:
-        $ prefect register flow --file my_flow.py --name My-Flow
+        $ prefect register flow --file my_flow.py --name My-Flow -l label1 -l label2
     """
 
     # Don't run extra `run` and `register` functions inside file
     with prefect.context({"loading_flow": True}):
         file_path = os.path.abspath(file)
-        flow_obj = extract_flow_from_file(file_path=file_path, flow_name=name)
+        flow = extract_flow_from_file(file_path=file_path, flow_name=name)
+
+    if getattr(flow, "run_config", None) is not None:
+        flow.run_config.labels.update(label)
+    else:
+        flow.environment.labels.update(label)
 
-    flow_obj.register(project_name=project)
+    flow.register(project_name=project)


Test patch:
diff --git a/tests/cli/test_register.py b/tests/cli/test_register.py
--- a/tests/cli/test_register.py
+++ b/tests/cli/test_register.py
@@ -1,7 +1,6 @@
-import os
-import tempfile
 from unittest.mock import MagicMock
 
+import pytest
 from click.testing import CliRunner
 
 from prefect.cli.register import register
@@ -21,26 +20,54 @@ def test_register_help():
     assert "Register flows" in result.output
 
 
-def test_register_flow():
+def test_register_flow_help():
     runner = CliRunner()
     result = runner.invoke(register, ["flow", "--help"])
     assert result.exit_code == 0
     assert "Register a flow" in result.output
 
 
-def test_register_flow_kwargs(monkeypatch, tmpdir):
-    monkeypatch.setattr("prefect.Client", MagicMock())
+@pytest.mark.parametrize("labels", [[], ["b", "c"]])
+@pytest.mark.parametrize("kind", ["run_config", "environment"])
+def test_register_flow_call(monkeypatch, tmpdir, kind, labels):
+    client = MagicMock()
+    monkeypatch.setattr("prefect.Client", MagicMock(return_value=client))
 
-    contents = """from prefect import Flow\nf=Flow('test-flow')"""
-
-    full_path = os.path.join(tmpdir, "flow.py")
+    if kind == "environment":
+        contents = (
+            "from prefect import Flow\n"
+            "from prefect.environments.execution import LocalEnvironment\n"
+            "from prefect.environments.storage import Local\n"
+            "f = Flow('test-flow', environment=LocalEnvironment(labels=['a']),\n"
+            "   storage=Local(add_default_labels=False))"
+        )
+    else:
+        contents = (
+            "from prefect import Flow\n"
+            "from prefect.run_configs import KubernetesRun\n"
+            "from prefect.environments.storage import Local\n"
+            "f = Flow('test-flow', run_config=KubernetesRun(labels=['a']),\n"
+            "   storage=Local(add_default_labels=False))"
+        )
 
+    full_path = str(tmpdir.join("flow.py"))
     with open(full_path, "w") as f:
         f.write(contents)
 
+    args = ["flow", "--file", full_path, "--name", "test-flow", "--project", "project"]
+    for l in labels:
+        args.extend(["-l", l])
+
     runner = CliRunner()
-    result = runner.invoke(
-        register,
-        ["flow", "--file", full_path, "--name", "test-flow", "--project", "project"],
-    )
+    result = runner.invoke(register, args)
+    assert client.register.called
+    assert client.register.call_args[1]["project_name"] == "project"
+
+    # Check additional labels are set if specified
+    flow = client.register.call_args[1]["flow"]
+    if kind == "run_config":
+        assert flow.run_config.labels == {"a", *labels}
+    else:
+        assert flow.environment.labels == {"a", *labels}
+
     assert result.exit_code == 0