Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions settings.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
syntax = "proto3";

package flyte.settings;

// SettingsService provides hierarchical configuration management across ROOT, DOMAIN, and PROJECT scopes.
//
// The service supports:
// - Hierarchical settings inheritance (ROOT -> DOMAIN -> PROJECT)
// - Local overrides at each scope level
// - Origin tracking for each effective setting
// - Simple two-method API: GetSettings and UpdateSettings
service SettingsService {
// GetSettings retrieves settings for a given scope, returning both effective
// (inherited + local) settings with origin metadata, and local overrides only.
//
// Scope is determined by the presence of domain and project fields:
// - Neither set: ROOT scope
// - Only domain set: DOMAIN scope
// - Both set: PROJECT scope
rpc GetSettings(GetSettingsRequest) returns (GetSettingsResponse);

// UpdateSettings replaces the complete set of local overrides for a given scope.
//
// Any setting not included in local_settings will be removed as an override
// and will inherit from parent scope. This is a full replacement operation,
// not a merge.
rpc UpdateSettings(UpdateSettingsRequest) returns (UpdateSettingsResponse);
}

// SettingValue represents a configuration value with support for common types.
message SettingValue {
oneof kind {
string string_value = 1;
int64 int_value = 2;
double double_value = 3;
bool bool_value = 4;
}
}

// SettingOrigin identifies where a setting value originates from in the hierarchy.
message SettingOrigin {
// scope_type is one of: "ROOT", "DOMAIN", or "PROJECT"
string scope_type = 1;

// domain is populated for DOMAIN and PROJECT scopes, empty for ROOT
string domain = 2;

// project is populated only for PROJECT scope, empty for ROOT and DOMAIN
string project = 3;
}

// EffectiveSetting represents a resolved setting value with its origin.
// This shows what value is actually in effect, considering inheritance.
message EffectiveSetting {
// key is the setting name (e.g., "default_queue", "run_concurrency")
string key = 1;

// value is the resolved setting value
SettingValue value = 2;

// origin indicates which scope this value comes from
SettingOrigin origin = 3;
}

// LocalSetting represents an explicit override at a specific scope.
message LocalSetting {
// key is the setting name
string key = 1;

// value is the override value
SettingValue value = 2;
}

// GetSettingsRequest specifies the scope to retrieve settings for.
message GetSettingsRequest {
// domain is optional. If empty, ROOT scope is queried.
string domain = 1;

// project is optional. Requires domain to be set. If both are set, PROJECT scope is queried.
string project = 2;
}

// GetSettingsResponse contains both effective settings (with inheritance) and local overrides.
message GetSettingsResponse {
// effective_settings contains all resolved settings with their origin metadata.
// This includes both inherited settings and local overrides.
repeated EffectiveSetting effective_settings = 1;

// local_settings contains only the explicit overrides set at this scope.
// Settings not present here are inherited from parent scopes.
repeated LocalSetting local_settings = 2;
}

// UpdateSettingsRequest replaces the complete set of local overrides for a scope.
message UpdateSettingsRequest {
// domain is optional. If empty, ROOT scope is updated.
string domain = 1;

// project is optional. Requires domain to be set. If both are set, PROJECT scope is updated.
string project = 2;

// local_settings is the complete list of local overrides for this scope.
// Any previous override not included here will be removed.
repeated LocalSetting local_settings = 3;
}

// UpdateSettingsResponse is currently empty but reserved for future extensions.
message UpdateSettingsResponse {}

// ============================================================================
// Available Settings Reference
// ============================================================================
//
// The following settings are supported in the Flyte settings hierarchy:
//
// Queue Configuration:
// - default_queue (string): The default queue for task execution
// - accessible_queues (string): Comma-separated list of accessible queues
// - queue_priority (int): Priority level for queue processing
//
// Concurrency Controls:
// - run_concurrency (int): Maximum concurrent runs
// - action_concurrency (int): Maximum concurrent actions
// - child_action_concurrency (int): Maximum concurrent child actions
//
// Cluster and Namespace:
// - cluster_mappings (string): JSON mapping of cluster assignments
// - namespace_mappings (string): JSON mapping of namespace assignments
// - namespace_auto_creation (bool): Enable automatic namespace creation on project creation
//
// Resource Defaults:
// - task_resource_defaults (string): JSON of default task resource requests/limits
// - service_account_defaults (string): Default service account name
// - metadata_bucket_defaults (string): Default metadata storage bucket
// - data_storage_location_defaults (string): Default data storage location
//
// Labels and Annotations:
// - labels (string): JSON map of Kubernetes labels to apply
// - annotations (string): JSON map of Kubernetes annotations to apply
//
// Runtime Configuration:
// - environment_variables (string): JSON map of environment variables
// - resource_quotas (string): JSON of resource quota definitions
// - interruptible (bool): Enable interruptible task execution
// - overwrite_cache (bool): Force cache overwrite
//
// Registry and Secrets:
// - container_registry (string): Container image registry URL
// - secrets (string): JSON map of secret references
120 changes: 120 additions & 0 deletions src/flyte/cli/_edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import os
import subprocess
import tempfile
from pathlib import Path

import rich_click as click

from flyte.cli import _common as common


@click.group()
def edit():
pass


@edit.command(cls=common.CommandBase)
@click.pass_obj
def settings(cfg: common.CLIConfig, project: str | None, domain: str | None):
"""Edit hierarchical settings interactively.

Opens settings in your $EDITOR, showing:
- Local overrides (uncommented)
- Inherited settings (commented with origin)

To create an override: uncomment a line and/or edit its value
To remove an override: comment out the line
"""
import flyte.remote as remote

# Determine scope
scope_desc = "ROOT"
if project and domain:
scope_desc = f"PROJECT({domain}/{project})"
elif domain:
scope_desc = f"DOMAIN({domain})"
elif project:
raise click.BadOptionUsage("project", "to set project settings, domain is required")

console = common.Console()
console.print(f"[bold]Editing settings for scope:[/bold] {scope_desc}")

# Get current settings
try:
settings = remote.Settings.get(project=project, domain=domain)
except Exception as e:
console.print(f"[red]Error fetching settings:[/red] {e}")
raise click.Abort

# Generate YAML for editing
yaml_content = settings.to_yaml()

# Get editor from environment
editor = os.environ.get("EDITOR", os.environ.get("VISUAL", "vi"))

# Create a temporary file for editing
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp_file:
tmp_path = Path(tmp_file.name)
tmp_file.write(yaml_content)

try:
# Open the editor
result = subprocess.run([editor, str(tmp_path)], check=False)
if result.returncode != 0:
console.print(f"[yellow]Editor exited with code {result.returncode}[/yellow]")

# Read the edited content
with open(tmp_path, "r") as f:
edited_content = f.read()

# Check if content changed
if edited_content.strip() == yaml_content.strip():
console.print("[dim]No changes detected.[/dim]")
return

# Parse the edited YAML to extract overrides
try:
overrides = remote.Settings.parse_yaml(edited_content)
except Exception as e:
console.print(f"[red]Error parsing edited YAML:[/red] {e}")
raise click.Abort

# Show changes
original_local = {s.key: s.value for s in settings.local_settings}
added = {k: v for k, v in overrides.items() if k not in original_local}
removed = {k: v for k, v in original_local.items() if k not in overrides}
modified = {
k: (original_local[k], v) for k, v in overrides.items() if k in original_local and original_local[k] != v
}

if added or removed or modified:
console.print("\n[bold]Changes to apply:[/bold]")
if added:
console.print("[green]Added overrides:[/green]")
for k, v in added.items():
console.print(f" + {k}: {v}")
if modified:
console.print("[yellow]Modified overrides:[/yellow]")
for k, (old, new) in modified.items():
console.print(f" ~ {k}: {old} → {new}")
if removed:
console.print("[red]Removed overrides (will inherit):[/red]")
for k, v in removed.items():
console.print(f" - {k}: {v}")

# Confirm and apply
if click.confirm("\nApply these changes?", default=True):
try:
settings.update(overrides)
console.print("[green]✓ Settings updated successfully[/green]")
except Exception as e:
console.print(f"[red]Error updating settings:[/red] {e}")
raise click.Abort
else:
console.print("[dim]Changes discarded.[/dim]")
else:
console.print("[dim]No changes detected.[/dim]")

finally:
# Clean up temporary file
tmp_path.unlink(missing_ok=True)
6 changes: 6 additions & 0 deletions src/flyte/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ._create import create
from ._delete import delete
from ._deploy import deploy
from ._edit import edit
from ._gen import gen
from ._get import get
from ._plugins import discover_and_register_plugins
Expand All @@ -37,6 +38,10 @@
"name": "Management of various objects.",
"commands": ["create", "get", "delete", "update"],
},
{
"name": "Settings management.",
"commands": ["edit"],
},
{
"name": "Build and deploy environments, tasks and images.",
"commands": ["build", "deploy"],
Expand Down Expand Up @@ -230,6 +235,7 @@ def main(
main.add_command(update) # type: ignore
main.add_command(serve) # type: ignore
main.add_command(prefetch) # type: ignore
main.add_command(edit) # type: ignore

# Discover and register CLI plugins from installed packages
discover_and_register_plugins(main)
2 changes: 2 additions & 0 deletions src/flyte/remote/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"RunDetails",
"Secret",
"SecretTypes",
"Settings",
"Task",
"TaskDetails",
"Trigger",
Expand All @@ -29,6 +30,7 @@
from ._project import Project
from ._run import Run, RunDetails
from ._secret import Secret, SecretTypes
from ._settings import Settings
from ._task import Task, TaskDetails
from ._trigger import Trigger
from ._user import User
Loading
Loading