Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3567fd3
feat(xtest): support platform-embedded otdfctl for migration to monorepo
dmihalcik-virtru Apr 15, 2026
0ea2e5d
fixup ruff format
dmihalcik-virtru Apr 16, 2026
7c20d73
fix(xtest): update remaining otdfctl references for platform monorepo…
dmihalcik-virtru Apr 16, 2026
cd0aec5
fixup pkg.go removes tag prefixes IIRC
dmihalcik-virtru Apr 16, 2026
1f02700
refactor(xtest): consolidate .version and .module-path into single .v…
dmihalcik-virtru Apr 16, 2026
9b9c9d7
feat(sdk-mgr): wire --source option through install artifact command
dmihalcik-virtru Apr 16, 2026
0c1b428
fix(setup-cli-tool): avoid script injection by using env vars for inp…
dmihalcik-virtru Apr 16, 2026
bf739dc
Apply suggestion from @gemini-code-assist[bot]
dmihalcik-virtru Apr 16, 2026
edb5e3a
feat(setup-cli-tool): support multiple platform-source Go versions
dmihalcik-virtru Apr 16, 2026
8f783c3
fix: address PR review findings for platform otdfctl migration
dmihalcik-virtru Apr 16, 2026
1e5fc53
fix(sdk-mgr): accept "standalone" as valid Go source in go_module_path
dmihalcik-virtru Apr 16, 2026
821c02c
docs(xtest): clarify auto mode resolves releases from standalone
dmihalcik-virtru Apr 16, 2026
2901544
fix(setup-cli-tool): require SHA match in auto-detect platform fallback
dmihalcik-virtru Apr 16, 2026
7e99e2d
fix: harden validation and error handling for platform otdfctl migration
dmihalcik-virtru Apr 16, 2026
13385f5
fixup ruff format
dmihalcik-virtru Apr 16, 2026
bfe1119
refactor(xtest): extract composite actions from xtest.yml
dmihalcik-virtru Apr 17, 2026
9cfc673
fix: add OT_ROOT_KEY env and guard fromJson on empty outputs
dmihalcik-virtru Apr 17, 2026
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
352 changes: 82 additions & 270 deletions .github/workflows/xtest.yml

Large diffs are not rendered by default.

223 changes: 223 additions & 0 deletions otdf-local/src/otdf_local/ci.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"""CI-specific commands for otdf-local.

These commands adapt the local environment management for GitHub Actions CI,
where the platform is already started by an external action and we only need
to start KAS instances as background processes.
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Annotated

import typer

from otdf_local.config.ports import Ports
from otdf_local.config.settings import Settings
from otdf_local.health.waits import WaitTimeoutError, wait_for_health
from otdf_local.services import get_kas_manager
from otdf_local.utils.console import (
print_error,
print_info,
print_success,
print_warning,
)
from otdf_local.utils.yaml import load_yaml, save_yaml, set_nested

ci_app = typer.Typer(
name="ci",
help="CI-specific commands for GitHub Actions workflows.",
no_args_is_help=True,
)


def _emit_github_output(key: str, value: str) -> None:
"""Write a key=value pair to $GITHUB_OUTPUT if available, else print to stdout."""
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output:
with open(github_output, "a") as f:
f.write(f"{key}={value}\n")
else:
# Fallback for local testing
print(f"{key}={value}", file=sys.stdout)


def _prepare_kas_template(
settings: Settings, root_key: str | None, ec_tdf_enabled: bool
) -> None:
"""Ensure the KAS template config has the right root key and EC TDF settings.

In CI, the platform config may have a root_key that differs from what
we want for additional KAS instances. This updates the platform config
in-place so that KASService._generate_config reads the correct root_key.
"""
if root_key:
config = load_yaml(settings.platform_config)
set_nested(config, "services.kas.root_key", root_key)
if ec_tdf_enabled:
set_nested(config, "services.kas.preview.ec_tdf_enabled", True)
save_yaml(settings.platform_config, config)
Comment on lines +47 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ec_tdf_enabled is silently ignored unless --root-key is provided.

The EC TDF toggle is only applied inside the if root_key: branch of _prepare_kas_template, and _prepare_kas_template itself is only called when root_key is truthy (line 164). A user passing --ec-tdf-enabled (or relying on the default True) without --root-key will get no EC TDF override written to the platform config.

Also, the function name _prepare_kas_template is misleading — it mutates settings.platform_config (i.e., opentdf-dev.yaml), not the KAS template (opentdf-kas-mode.yaml). Consider renaming to _prepare_platform_config and decoupling the two flags:

🐛 Proposed fix
-def _prepare_kas_template(
-    settings: Settings, root_key: str | None, ec_tdf_enabled: bool
-) -> None:
-    """Ensure the KAS template config has the right root key and EC TDF settings.
-    ...
-    """
-    if root_key:
-        config = load_yaml(settings.platform_config)
-        set_nested(config, "services.kas.root_key", root_key)
-        if ec_tdf_enabled:
-            set_nested(config, "services.kas.preview.ec_tdf_enabled", True)
-        save_yaml(settings.platform_config, config)
+def _prepare_platform_config(
+    settings: Settings, root_key: str | None, ec_tdf_enabled: bool
+) -> None:
+    """Apply CI overrides to the platform config read by KASService._generate_config."""
+    if not root_key and not ec_tdf_enabled:
+        return
+    config = load_yaml(settings.platform_config)
+    if root_key:
+        set_nested(config, "services.kas.root_key", root_key)
+    if ec_tdf_enabled:
+        set_nested(config, "services.kas.preview.ec_tdf_enabled", True)
+    save_yaml(settings.platform_config, config)
@@
-    # Update root key in platform config if provided
-    if root_key:
-        _prepare_kas_template(settings, root_key, ec_tdf_enabled)
+    # Apply CI overrides to the platform config
+    _prepare_platform_config(settings, root_key, ec_tdf_enabled)

Also applies to: 163-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@otdf-local/src/otdf_local/ci.py` around lines 47 - 61, _prepare_kas_template
currently only applies ec_tdf_enabled when root_key is provided, so passing
--ec-tdf-enabled alone doesn't update settings.platform_config; change the logic
in _prepare_kas_template (or rename it to _prepare_platform_config) to always
load settings.platform_config and set "services.kas.preview.ec_tdf_enabled" when
ec_tdf_enabled is True regardless of root_key, and only conditionally set
"services.kas.root_key" when root_key is provided; update call sites that assume
the old behavior and consider renaming the function from _prepare_kas_template
to _prepare_platform_config to better reflect that it mutates
settings.platform_config.



@ci_app.command("start-kas")
def start_kas(
platform_dir: Annotated[
Path,
typer.Option(
"--platform-dir",
help="Path to the platform checkout (must contain opentdf-kas-mode.yaml)",
envvar="OTDF_LOCAL_PLATFORM_DIR",
),
],
root_key: Annotated[
str | None,
typer.Option(
"--root-key",
help="Root key for KAS instances (overrides platform config value)",
envvar="OT_ROOT_KEY",
),
] = None,
ec_tdf_enabled: Annotated[
bool,
typer.Option(
"--ec-tdf-enabled/--no-ec-tdf",
help="Enable EC TDF support",
),
] = True,
key_management: Annotated[
bool,
typer.Option(
"--key-management/--no-key-management",
help="Enable key management on km1/km2 instances",
),
] = False,
log_type: Annotated[
str,
typer.Option(
"--log-type",
help="Log format type (json, text)",
),
] = "json",
health_timeout: Annotated[
int,
typer.Option(
"--health-timeout",
help="Seconds to wait for each KAS instance to become healthy",
),
] = 60,
Comment on lines +89 to +109
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

key_management and log_type options are accepted but never used.

Both parameters are declared in the CLI signature but never referenced in the function body (lines 118-223). Per the AI summary, the new xtest/setup-kas-instances/action.yaml forwards --key-management (and this file’s docstring advertises all 6 instances incl. km1/km2), so callers will believe they’re toggling behavior that has no effect. Either wire them through to KASService.start() / _generate_config (e.g., to conditionally start km1/km2 or to select log format), or drop the options so the caller knows they are no-ops.

At minimum, key_management=False should probably filter kas_names to exclude km1/km2 when the caller did not opt in — otherwise the --key-management/--no-key-management default of False is misleading vs. the default behavior of starting all 6 KAS instances.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@otdf-local/src/otdf_local/ci.py` around lines 89 - 109, The CLI options
key_management and log_type are accepted but unused; either propagate them into
KASService.start() and/or KASService._generate_config() or remove the CLI flags.
At minimum, when key_management is False filter the kas_names list to exclude
"km1" and "km2" before creating/starting services, and pass log_type through to
the service start/config generation so the selected log format (json/text) is
applied; update calls to KASService.start(...) and
KASService._generate_config(...) to accept and use these parameters (or remove
the options and their annotations if you choose to drop support).

instances: Annotated[
str | None,
typer.Option(
"--instances",
help="Comma-separated KAS instance names (default: all)",
),
] = None,
) -> None:
"""Start KAS instances for CI and emit GitHub Actions outputs.

Expects the platform to already be running (started by start-up-with-containers).
Starts all 6 KAS instances (alpha, beta, gamma, delta, km1, km2) as background
processes, waits for each to pass health checks, and emits log file paths as
GitHub Actions step outputs.

Output keys (written to $GITHUB_OUTPUT):
kas-alpha-log-file, kas-beta-log-file, kas-gamma-log-file,
kas-delta-log-file, kas-km1-log-file, kas-km2-log-file
"""
platform_dir = platform_dir.resolve()
if not platform_dir.is_dir():
print_error(f"Platform directory does not exist: {platform_dir}")
raise typer.Exit(1)

# Check for required template files
kas_template = platform_dir / "opentdf-kas-mode.yaml"
platform_config = platform_dir / "opentdf-dev.yaml"
if not kas_template.exists():
# Fall back to opentdf.yaml if opentdf-kas-mode.yaml doesn't exist
kas_template_alt = platform_dir / "opentdf.yaml"
if kas_template_alt.exists():
print_info(
f"Using {kas_template_alt} as KAS template (opentdf-kas-mode.yaml not found)"
)
else:
print_error(
f"Neither opentdf-kas-mode.yaml nor opentdf.yaml found in {platform_dir}"
)
raise typer.Exit(1)

if not platform_config.exists():
# Try opentdf.yaml as fallback
platform_config_alt = platform_dir / "opentdf.yaml"
if platform_config_alt.exists():
platform_config = platform_config_alt
Comment on lines +135 to +154
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variables kas_template and platform_config are calculated through fallback logic but are never used to configure the settings object or passed to any subsequent function. If the Settings class does not implement the same fallback logic internally, _prepare_kas_template (which uses settings.platform_config) might operate on a different file than the one validated here. If Settings already handles this, these lines are redundant.

Comment on lines +134 to +154
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Template/config fallback logic is effectively dead code.

Two issues in this block:

  1. kas_template (line 135) is assigned but never used beyond .exists() — and the kas_template_alt fallback on lines 139-143 is only used to print an info message; nothing downstream (e.g., KASService._generate_config) is told to consult the alt path. If opentdf-kas-mode.yaml is absent, the command keeps going and will likely fail later when the service actually tries to read it.
  2. The platform_config local (lines 136, 150-154) is never consumed. _prepare_kas_template uses settings.platform_config, which is a computed property hard-wired to platform_dir / "opentdf-dev.yaml" (see otdf-local/src/otdf_local/config/settings.py:117-119). So the opentdf.yaml fallback on lines 150-154 has no effect — if opentdf-dev.yaml is missing and --root-key is given, load_yaml(settings.platform_config) will raise.

Either wire the resolved paths into Settings/KASService (e.g., override the config location) or simplify this to a hard existence check with a clear error. As written, the fallbacks give false reassurance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@otdf-local/src/otdf_local/ci.py` around lines 134 - 154, The fallback logic
for kas_template and platform_config is ineffective: kas_template is only
checked but never passed along and platform_config local is never consumed
because _prepare_kas_template and KASService._generate_config read
settings.platform_config (a computed path). Fix by making the resolved paths
authoritative—either set/override settings.platform_config (or an explicit
Settings field) with the chosen platform_config and similarly ensure the KAS
template path selected (kas_template or kas_template_alt) is supplied to
KASService._generate_config/_prepare_kas_template (or to Settings) so downstream
readers use the actual file, or remove the misleading fallbacks and replace with
a single clear existence check that raises an error; update references to
kas_template, kas_template_alt, platform_config, settings.platform_config,
_prepare_kas_template and KASService._generate_config accordingly.


# Build settings with CI-specific overrides
# We use a fresh xtest_root derived from this package's location
settings = Settings(
platform_dir=platform_dir,
)
settings.ensure_directories()

# Update root key in platform config if provided
if root_key:
_prepare_kas_template(settings, root_key, ec_tdf_enabled)

# Determine which instances to start
if instances:
kas_names = [n.strip() for n in instances.split(",")]
for name in kas_names:
if name not in Ports.all_kas_names():
print_error(f"Unknown KAS instance: {name}")
raise typer.Exit(1)
else:
kas_names = Ports.all_kas_names()

# Start KAS instances
print_info(f"Starting KAS instances: {', '.join(kas_names)}...")
kas_manager = get_kas_manager(settings)

failed = []
for name in kas_names:
kas = kas_manager.get(name)
if kas is None:
print_error(f"KAS instance {name} not found in manager")
failed.append(name)
continue
if not kas.start():
print_error(f"Failed to start KAS {name}")
failed.append(name)

if failed:
print_error(f"Failed to start: {', '.join(failed)}")
raise typer.Exit(1)

# Wait for health
print_info("Waiting for KAS health checks...")
unhealthy = []
for name in kas_names:
port = Ports.get_kas_port(name)
try:
wait_for_health(
f"http://localhost:{port}/healthz",
timeout=health_timeout,
service_name=f"KAS {name}",
)
except WaitTimeoutError as e:
print_warning(str(e))
unhealthy.append(name)

if unhealthy:
print_error(f"KAS instances failed health check: {', '.join(unhealthy)}")
raise typer.Exit(1)

print_success(f"All {len(kas_names)} KAS instances are healthy")

# Emit outputs
for name in kas_names:
log_path = settings.get_kas_log_path(name)
output_key = f"kas-{name}-log-file"
_emit_github_output(output_key, str(log_path))

print_success("CI KAS startup complete")
3 changes: 3 additions & 0 deletions otdf-local/src/otdf_local/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from rich.live import Live

from otdf_local import __version__
from otdf_local.ci import ci_app
from otdf_local.config.ports import Ports
from otdf_local.config.settings import get_settings
from otdf_local.health.waits import WaitTimeoutError, wait_for_health, wait_for_port
Expand Down Expand Up @@ -43,6 +44,8 @@
pretty_exceptions_enable=sys.stderr.isatty(),
)

app.add_typer(ci_app, name="ci")


def _show_provision_error(result: ProvisionResult, target: str) -> None:
"""Display provisioning error with stderr details."""
Expand Down
2 changes: 1 addition & 1 deletion otdf-sdk-mgr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ otdf-sdk-mgr java-fixup

## How Release Installs Work

- **Go**: Writes a `.version` file; `cli.sh`/`otdfctl.sh` use `go run github.com/opentdf/otdfctl@{version}` (no local compilation needed, Go caches the binary)
- **Go**: Writes a `.version` file containing `module-path@version` (e.g., `github.com/opentdf/otdfctl@v0.24.0`); `cli.sh`/`otdfctl.sh` use `go run <module>@<version>` (no local compilation needed, Go caches the binary). The module path is `github.com/opentdf/platform/otdfctl` for platform-embedded releases or `github.com/opentdf/otdfctl` for standalone releases.
- **JS**: Runs `npm install @opentdf/ctl@{version}` into the dist directory; `cli.sh` uses `npx` from local `node_modules/`
- **Java**: Downloads `cmdline.jar` from GitHub Releases; `cli.sh` uses `java -jar cmdline.jar`

Expand Down
36 changes: 36 additions & 0 deletions otdf-sdk-mgr/src/otdf_sdk_mgr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,39 @@ def java_fixup(
from otdf_sdk_mgr.java_fixup import post_checkout_java_fixup

post_checkout_java_fixup(base_dir)


@app.command("go-fixup")
def go_fixup_cmd(
platform_dir: Annotated[
Path,
typer.Option("--platform-dir", help="Path to the platform checkout root"),
],
heads: Annotated[
Optional[str],
typer.Option(
"--heads",
help="JSON list of head version tags to process (e.g. '[\"main\"]')",
),
] = None,
base_dir: Annotated[
Optional[Path],
typer.Argument(help="Base directory for Go source trees"),
] = None,
) -> None:
"""Bridge Go client go.mod to server shared modules for head builds.

Performs go mod edit -replace + go mod tidy for each head version,
pointing platform module imports at the local platform checkout.
Only needed for standalone otdfctl checkouts.
"""
import json as json_mod

from otdf_sdk_mgr.go_fixup import go_fixup

heads_list = json_mod.loads(heads) if heads else None
try:
go_fixup(platform_dir, heads=heads_list, base_dir=base_dir)
except (FileNotFoundError, subprocess.CalledProcessError) as e:
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1) from e
Comment on lines +122 to +131
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle malformed --heads JSON gracefully.

If --heads is provided but not valid JSON (or not a list), json_mod.loads(heads) raises json.JSONDecodeError and go_fixup may in turn raise a TypeError, both of which escape the current except clause and surface as an unhandled traceback to CI.

🛡️ Proposed fix
     import json as json_mod
 
     from otdf_sdk_mgr.go_fixup import go_fixup
 
-    heads_list = json_mod.loads(heads) if heads else None
+    heads_list: list[str] | None = None
+    if heads:
+        try:
+            heads_list = json_mod.loads(heads)
+        except json_mod.JSONDecodeError as e:
+            typer.echo(f"Error: --heads must be valid JSON: {e}", err=True)
+            raise typer.Exit(1) from e
+        if not isinstance(heads_list, list) or not all(isinstance(h, str) for h in heads_list):
+            typer.echo("Error: --heads must be a JSON list of strings", err=True)
+            raise typer.Exit(1)
     try:
         go_fixup(platform_dir, heads=heads_list, base_dir=base_dir)
     except (FileNotFoundError, subprocess.CalledProcessError) as e:
         typer.echo(f"Error: {e}", err=True)
         raise typer.Exit(1) from e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import json as json_mod
from otdf_sdk_mgr.go_fixup import go_fixup
heads_list = json_mod.loads(heads) if heads else None
try:
go_fixup(platform_dir, heads=heads_list, base_dir=base_dir)
except (FileNotFoundError, subprocess.CalledProcessError) as e:
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1) from e
import json as json_mod
from otdf_sdk_mgr.go_fixup import go_fixup
heads_list: list[str] | None = None
if heads:
try:
heads_list = json_mod.loads(heads)
except json_mod.JSONDecodeError as e:
typer.echo(f"Error: --heads must be valid JSON: {e}", err=True)
raise typer.Exit(1) from e
if not isinstance(heads_list, list) or not all(isinstance(h, str) for h in heads_list):
typer.echo("Error: --heads must be a JSON list of strings", err=True)
raise typer.Exit(1)
try:
go_fixup(platform_dir, heads=heads_list, base_dir=base_dir)
except (FileNotFoundError, subprocess.CalledProcessError) as e:
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1) from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@otdf-sdk-mgr/src/otdf_sdk_mgr/cli.py` around lines 122 - 131, The code
currently calls json_mod.loads(heads) and passes the result to go_fixup without
handling json.JSONDecodeError or invalid types, causing unhandled tracebacks;
update the CLI logic around heads_list and the call to go_fixup (symbols:
heads_list, json_mod.loads, go_fixup, typer.Exit) to catch json.JSONDecodeError
and TypeError, validate that parsed heads_list is a list (or None) before
calling go_fixup, and on error use typer.echo to print a clear message and raise
typer.Exit(1) so malformed JSON or wrong types fail gracefully.

6 changes: 5 additions & 1 deletion otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,16 @@ def artifact(
dist_name: Annotated[
Optional[str], typer.Option("--dist-name", help="Override dist directory name")
] = None,
source: Annotated[
Optional[str],
typer.Option(help='Source repo for Go CLI (e.g., "platform" for monorepo)'),
] = None,
) -> None:
"""Install a single SDK version (used by CI)."""
from otdf_sdk_mgr.installers import InstallError, cmd_install

try:
cmd_install(sdk, version, dist_name=dist_name)
cmd_install(sdk, version, dist_name=dist_name, source=source)
except InstallError as e:
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1)
13 changes: 12 additions & 1 deletion otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
import os
from typing import Annotated, Any, Optional

import typer
Expand Down Expand Up @@ -112,10 +113,20 @@ def resolve_versions(
raise typer.Exit(2)
infix = SDK_TAG_INFIXES.get(sdk)

# Allow overriding the Go SDK source via OTDFCTL_SOURCE env var
# (standalone otdfctl repo vs platform monorepo)
go_source = os.environ.get("OTDFCTL_SOURCE") if sdk == "go" else None
if go_source and go_source not in ("standalone", "platform"):
typer.echo(
f"Warning: unrecognized OTDFCTL_SOURCE={go_source!r}; expected 'platform' or 'standalone'",
err=True,
)
go_source = None

results: list[ResolveResult] = []
shas: set[str] = set()
for version in tags:
v = resolve(sdk, version, infix)
v = resolve(sdk, version, infix, go_source=go_source)
if is_resolve_success(v):
env = lookup_additional_options(sdk, v["tag"])
if env:
Expand Down
48 changes: 47 additions & 1 deletion otdf-sdk-mgr/src/otdf_sdk_mgr/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ def get_sdk_dirs() -> dict[str, Path]:
"java": "opentdf/java-sdk",
}

GO_INSTALL_PREFIX = "go run github.com/opentdf/otdfctl"
GO_INSTALL_PREFIX_STANDALONE = "go run github.com/opentdf/otdfctl"
GO_INSTALL_PREFIX_PLATFORM = "go run github.com/opentdf/platform/otdfctl"

GO_MODULE_PATH = "github.com/opentdf/otdfctl"
GO_MODULE_PATH_PLATFORM = "github.com/opentdf/platform/otdfctl"

LTS_VERSIONS: dict[str, str] = {
"go": "0.24.0",
Expand Down Expand Up @@ -111,4 +115,46 @@ def get_sdk_dirs() -> dict[str, Path]:
"platform": "service",
}

# When resolving go versions from the platform repo, use "otdfctl" infix
# (tags are otdfctl/vX.Y.Z in the platform monorepo)
SDK_TAG_INFIXES_PLATFORM_GO = "otdfctl"

_VALID_GO_SOURCES = {None, "standalone", "platform"}


def _validate_go_source(source: str | None) -> None:
"""Raise ValueError if source is not a recognised Go source."""
if source not in _VALID_GO_SOURCES:
raise ValueError(f"Invalid Go source {source!r}; expected one of {_VALID_GO_SOURCES}")


def go_git_url(source: str | None = None) -> str:
"""Return the git URL for Go SDK resolution based on source.

Args:
source: "platform" to use the platform monorepo, None/"standalone" for the
standalone otdfctl repo.
"""
_validate_go_source(source)
if source == "platform":
return SDK_GIT_URLS["platform"]
return SDK_GIT_URLS["go"]


def go_tag_infix(source: str | None = None) -> str | None:
"""Return the tag infix for Go SDK resolution based on source."""
_validate_go_source(source)
if source == "platform":
return SDK_TAG_INFIXES_PLATFORM_GO
return None


def go_module_path(source: str | None = None) -> str:
"""Return the Go module path based on source."""
_validate_go_source(source)
if source == "platform":
return GO_MODULE_PATH_PLATFORM
return GO_MODULE_PATH


ALL_SDKS = ["go", "js", "java"]
Loading
Loading