Repository: skypilot-org/skypilot

Issue:
[UX] Make `labels` work in task.yaml without the requirement for specifying `cloud`
<!-- Describe the bug report / feature request here -->


Currently, SkyPilot disallows the following because `cloud` is not specified:
```yaml
resources:
  labels:
    abc: test
```

This does not make sense because we may want the labels to work across clouds. The original reason we have this constraint is because different clouds can have different requirements for the label format. 
One proposal is: When `cloud` is not specified, we just check the labels with all enabled clouds

<!-- If relevant, fill in versioning info to help us troubleshoot -->
_Version & Commit info:_
* `sky -v`: PLEASE_FILL_IN
* `sky -c`: PLEASE_FILL_IN


Discussion:

Patch:
diff --git a/sky/resources.py b/sky/resources.py
index 2f19cd1a..e9a522ce 100644
--- a/sky/resources.py
+++ b/sky/resources.py
@@ -966,20 +966,22 @@ class Resources:
         """
         if not self._labels:
             return
-
-        if self.cloud is None:
-            # Because each cloud has its own label format, we cannot validate
-            # the labels without knowing the cloud.
-            with ux_utils.print_exception_no_traceback():
-                raise ValueError(
-                    'Cloud must be specified when labels are provided.')
-
-        # Check if the label key value pairs are valid.
+        if self.cloud is not None:
+            validated_clouds = [self.cloud]
+        else:
+            # If no specific cloud is set, validate label against ALL clouds.
+            # The label will be dropped if invalid for any one of the cloud
+            validated_clouds = sky_check.get_cached_enabled_clouds_or_refresh()
         invalid_table = log_utils.create_table(['Label', 'Reason'])
         for key, value in self._labels.items():
-            valid, err_msg = self.cloud.is_label_valid(key, value)
-            if not valid:
-                invalid_table.add_row([f'{key}: {value}', err_msg])
+            for cloud in validated_clouds:
+                valid, err_msg = cloud.is_label_valid(key, value)
+                if not valid:
+                    invalid_table.add_row([
+                        f'{key}: {value}',
+                        f'Label rejected due to {cloud}: {err_msg}'
+                    ])
+                    break
         if len(invalid_table.rows) > 0:
             with ux_utils.print_exception_no_traceback():
                 raise ValueError(


Test patch:
diff --git a/tests/unit_tests/test_resources.py b/tests/unit_tests/test_resources.py
index 01b83132..5006fc45 100644
--- a/tests/unit_tests/test_resources.py
+++ b/tests/unit_tests/test_resources.py
@@ -6,6 +6,7 @@ from unittest import mock
 import pytest
 
 from sky import clouds
+from sky import global_user_state
 from sky import skypilot_config
 from sky.resources import Resources
 from sky.utils import resources_utils
@@ -34,7 +35,8 @@ def test_get_reservations_available_resources():
 
 
 def _run_label_test(allowed_labels: Dict[str, str],
-                    invalid_labels: Dict[str, str], cloud: clouds.Cloud):
+                    invalid_labels: Dict[str, str],
+                    cloud: clouds.Cloud = None):
     """Run a test for labels with the given allowed and invalid labels."""
     r_allowed = Resources(cloud=cloud, labels=allowed_labels)  # Should pass
     assert r_allowed.labels == allowed_labels, ('Allowed labels '
@@ -92,6 +94,32 @@ def test_kubernetes_labels_resources():
     _run_label_test(allowed_labels, invalid_labels, cloud)
 
 
+def test_no_cloud_labels_resources():
+    global_user_state.set_enabled_clouds(['aws', 'gcp'])
+    allowed_labels = {
+        **GLOBAL_VALID_LABELS,
+    }
+    invalid_labels = {
+        **GLOBAL_INVALID_LABELS,
+        'aws:cannotstartwithaws': 'value',
+        'domain/key': 'value',  # Invalid for GCP
+    }
+    _run_label_test(allowed_labels, invalid_labels)
+
+
+def test_no_cloud_labels_resources_single_enabled_cloud():
+    global_user_state.set_enabled_clouds(['aws'])
+    allowed_labels = {
+        **GLOBAL_VALID_LABELS,
+        'domain/key': 'value',  # Valid for AWS
+    }
+    invalid_labels = {
+        **GLOBAL_INVALID_LABELS,
+        'aws:cannotstartwithaws': 'value',
+    }
+    _run_label_test(allowed_labels, invalid_labels)
+
+
 @mock.patch('sky.clouds.service_catalog.instance_type_exists',
             return_value=True)
 @mock.patch('sky.clouds.service_catalog.get_accelerators_from_instance_type',