diff --git a/.github/workflows/xtest.yml b/.github/workflows/xtest.yml
index 773448892..c008e15cd 100644
--- a/.github/workflows/xtest.yml
+++ b/.github/workflows/xtest.yml
@@ -28,6 +28,11 @@ on:
type: string
default: all
description: "SDK to focus on (go, js, java, all)"
+ otdfctl-source:
+ required: false
+ type: string
+ default: auto
+ description: "otdfctl source: 'auto' (standalone for releases, detect platform for head builds), 'standalone', or 'platform'"
workflow_call:
inputs:
platform-ref:
@@ -50,6 +55,10 @@ on:
required: false
type: string
default: all
+ otdfctl-source:
+ required: false
+ type: string
+ default: auto
schedule:
- cron: "30 6 * * *" # 0630 UTC
- cron: "0 5 * * 1,3" # 500 UTC (Monday, Wednesday)
@@ -78,6 +87,14 @@ jobs:
JS_REF: "${{ inputs.js-ref }}"
OTDFCTL_REF: "${{ inputs.otdfctl-ref }}"
JAVA_REF: "${{ inputs.java-ref }}"
+ # When explicitly set to 'platform', tells the Go resolver to resolve
+ # against opentdf/platform tags instead of the standalone otdfctl repo.
+ # In 'auto' mode, releases resolve from standalone; the detect-otdfctl
+ # step later probes the platform checkout for an embedded otdfctl
+ # directory, and setup-cli-tool acts on this only for versions that need
+ # a source checkout (head or artifact-install failure), reusing the
+ # platform checkout only when the resolved SHA matches.
+ OTDFCTL_SOURCE: "${{ inputs.otdfctl-source == 'platform' && 'platform' || '' }}"
steps:
- name: Validate focus-sdk input
if: ${{ inputs.focus-sdk != '' }}
@@ -170,7 +187,7 @@ jobs:
core.summary.addHeading('Versions under Test', 3);
- function artifactLink(sdkType, tag, release, head) {
+ function artifactLink(sdkType, tag, release, head, source) {
if (head || !release) return '';
const v = tag.replace(/^v/, '');
if (sdkType === 'js') {
@@ -182,7 +199,10 @@ jobs:
return `Maven Central`;
}
if (sdkType === 'go') {
- const url = `https://pkg.go.dev/github.com/opentdf/otdfctl@${encodeURIComponent(tag)}`;
+ const modulePath = source === 'platform'
+ ? `github.com/opentdf/platform/otdfctl`
+ : `github.com/opentdf/otdfctl`;
+ const url = `https://pkg.go.dev/${modulePath}@${encodeURIComponent(tag)}`;
return `pkg.go.dev`;
}
return '';
@@ -198,14 +218,15 @@ jobs:
const tagToSha = {};
const headTags = [];
- for (const { tag, head, sha, alias, err, release } of refInfo) {
- const sdkRepoUrl = `https://github.com/opentdf/${encodeURIComponent(sdkType == 'js' ? 'web-sdk' : sdkType == 'go' ? 'otdfctl' : sdkType == 'java' ? 'java-sdk' : sdkType)}`;
+ for (const { tag, head, sha, alias, err, release, source } of refInfo) {
+ const goRepoName = source === 'platform' ? 'platform' : 'otdfctl';
+ const sdkRepoUrl = `https://github.com/opentdf/${encodeURIComponent(sdkType == 'js' ? 'web-sdk' : sdkType == 'go' ? goRepoName : sdkType == 'java' ? 'java-sdk' : sdkType)}`;
const sdkLink = `${htmlEscape(sdkType)}`;
const commitLink = sha ? `${htmlEscape(sha.substring(0, 7))}` : ' . ';
const tagLink = (release && tag)
? `${htmlEscape(tag)}`
: tag ? htmlEscape(tag) : 'N/A';
- const artifactCell = artifactLink(sdkType, tag, release, head);
+ const artifactCell = artifactLink(sdkType, tag, release, head, source);
table.push([sdkLink, tagLink, commitLink, alias || 'N/A', artifactCell || 'N/A', err || 'N/A']);
if (err) {
errorCount += 1;
@@ -290,6 +311,43 @@ jobs:
with:
node-version: "22.x"
+ ######## DETECT PLATFORM-EMBEDDED OTDFCTL #############
+ - name: Detect platform-embedded otdfctl
+ id: detect-otdfctl
+ run: |
+ if [[ "$OTDFCTL_SOURCE_INPUT" == "auto" || -z "$OTDFCTL_SOURCE_INPUT" ]]; then
+ if [ -d "$PLATFORM_DIR/otdfctl" ] && [ -f "$PLATFORM_DIR/otdfctl/go.mod" ]; then
+ echo "otdfctl found in platform checkout at $PLATFORM_DIR/otdfctl"
+ echo "otdfctl-source=platform" >> "$GITHUB_OUTPUT"
+ echo "otdfctl-dir=$(pwd)/$PLATFORM_DIR/otdfctl" >> "$GITHUB_OUTPUT"
+ platform_sha=$(git -C "$PLATFORM_DIR" rev-parse HEAD) || {
+ echo "::error::Failed to get SHA from platform checkout at $PLATFORM_DIR"
+ exit 1
+ }
+ echo "otdfctl-sha=$platform_sha" >> "$GITHUB_OUTPUT"
+ else
+ echo "otdfctl not found in platform checkout; using standalone repo"
+ echo "otdfctl-source=standalone" >> "$GITHUB_OUTPUT"
+ fi
+ elif [[ "$OTDFCTL_SOURCE_INPUT" == "platform" ]]; then
+ if [ -z "$PLATFORM_DIR" ] || [ ! -d "$PLATFORM_DIR/otdfctl" ] || [ ! -f "$PLATFORM_DIR/otdfctl/go.mod" ]; then
+ echo "::error::otdfctl-source=platform requested but ${PLATFORM_DIR:-}/otdfctl does not exist or lacks go.mod"
+ exit 1
+ fi
+ echo "otdfctl-source=platform" >> "$GITHUB_OUTPUT"
+ echo "otdfctl-dir=$(pwd)/$PLATFORM_DIR/otdfctl" >> "$GITHUB_OUTPUT"
+ platform_sha=$(git -C "$PLATFORM_DIR" rev-parse HEAD) || {
+ echo "::error::Failed to get SHA from platform checkout at $PLATFORM_DIR"
+ exit 1
+ }
+ echo "otdfctl-sha=$platform_sha" >> "$GITHUB_OUTPUT"
+ else
+ echo "otdfctl-source=standalone" >> "$GITHUB_OUTPUT"
+ fi
+ env:
+ OTDFCTL_SOURCE_INPUT: ${{ inputs.otdfctl-source }}
+ PLATFORM_DIR: ${{ steps.run-platform.outputs.platform-working-dir }}
+
######### CHECKOUT JS CLI #############
- name: Configure js-sdk
id: configure-js
@@ -324,6 +382,8 @@ jobs:
path: otdftests/xtest/sdk
sdk: go
version-info: "${{ needs.resolve-versions.outputs.go }}"
+ platform-otdfctl-dir: ${{ steps.detect-otdfctl.outputs.otdfctl-dir }}
+ platform-otdfctl-sha: ${{ steps.detect-otdfctl.outputs.otdfctl-sha }}
- name: Cache Go modules
if: fromJson(steps.configure-go.outputs.heads)[0] != null
@@ -345,11 +405,15 @@ jobs:
OTDFCTL_HEADS: ${{ steps.configure-go.outputs.heads }}
- name: Replace otdfctl go.mod packages, but only at head version of platform
- if: fromJson(steps.configure-go.outputs.heads)[0] != null && env.FOCUS_SDK == 'go' && contains(fromJSON(needs.resolve-versions.outputs.heads), matrix.platform-tag)
+ if: >-
+ steps.detect-otdfctl.outputs.otdfctl-source != 'platform'
+ && fromJson(steps.configure-go.outputs.heads)[0] != null
+ && env.FOCUS_SDK == 'go'
+ && contains(fromJSON(needs.resolve-versions.outputs.heads), matrix.platform-tag)
env:
PLATFORM_WORKING_DIR: ${{ steps.run-platform.outputs.platform-working-dir }}
run: |-
- echo "Replacing go.mod packages..."
+ echo "Replacing go.mod packages (standalone otdfctl)..."
PLATFORM_DIR_ABS="$(pwd)/${PLATFORM_WORKING_DIR}"
OTDFCTL_DIR_ABS="$(pwd)/otdftests/xtest/sdk/go/src/"
echo "PLATFORM_DIR_ABS: $PLATFORM_DIR_ABS"
diff --git a/otdf-sdk-mgr/README.md b/otdf-sdk-mgr/README.md
index ee2a2a8b7..1e3fd253f 100644
--- a/otdf-sdk-mgr/README.md
+++ b/otdf-sdk-mgr/README.md
@@ -1,6 +1,8 @@
# otdf-sdk-mgr
-SDK artifact management CLI for OpenTDF cross-client tests. Installs SDK CLIs from **released artifacts** (fast, deterministic) or **source** (for branch/PR testing). Both modes produce the same `sdk/{go,java,js}/dist/{version}/` directory structure.
+SDK artifact management CLI for OpenTDF cross-client tests.
+Installs SDK CLIs from **released artifacts** (fast, deterministic) or **source** (for branch/PR testing).
+Both modes produce the same `sdk/{go,java,js}/dist/{version}/` directory structure.
## Installation
@@ -56,7 +58,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 @` (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`
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py
index e3950d717..e62ae2464 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py
@@ -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)
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py
index 19188b124..35e44a1ca 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
+import os
from typing import Annotated, Any, Optional
import typer
@@ -112,10 +113,20 @@ def resolve_versions(
raise typer.Exit(2)
infix = SDK_TAG_INFIXES.get(sdk)
+ # Set the Go SDK source via OTDFCTL_SOURCE env var
+ # (standalone otdfctl repo vs platform monorepo). When unset, defaults to standalone.
+ 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"Error: unrecognized OTDFCTL_SOURCE={go_source!r}; expected 'platform' or 'standalone'",
+ err=True,
+ )
+ raise typer.Exit(2)
+
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:
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/config.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/config.py
index adf6c8b1f..1046a5ef8 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/config.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/config.py
@@ -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",
@@ -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"]
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/installers.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/installers.py
index e7c22ae09..6136d4015 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/installers.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/installers.py
@@ -11,9 +11,11 @@
from pathlib import Path
from otdf_sdk_mgr.config import (
+ GO_MODULE_PATH_PLATFORM,
LTS_VERSIONS,
get_sdk_dir,
get_sdk_dirs,
+ go_module_path,
)
from otdf_sdk_mgr.checkout import checkout_sdk_branch
from otdf_sdk_mgr.registry import list_go_versions, list_java_github_releases, list_js_versions
@@ -24,33 +26,48 @@ class InstallError(Exception):
"""Raised when SDK installation fails."""
-def install_go_release(version: str, dist_dir: Path) -> None:
+def install_go_release(version: str, dist_dir: Path, source: str | None = None) -> None:
"""Install a Go CLI release by writing a .version file.
The cli.sh and otdfctl.sh wrappers read .version and use
- `go run github.com/opentdf/otdfctl@{version}` instead of a local binary.
+ `go run @{version}` instead of a local binary.
+ The .version file contains `module-path@version`
+ (e.g., `github.com/opentdf/otdfctl@v0.24.0`).
+
+ Args:
+ version: Version string (e.g., "v0.24.0" or "otdfctl/v0.24.0").
+ dist_dir: Target distribution directory.
+ source: "platform" to use the platform monorepo module path,
+ None or "standalone" for standalone.
"""
go_dir = get_sdk_dir() / "go"
dist_dir.mkdir(parents=True, exist_ok=True)
+ # Strip tag infix (e.g., "otdfctl/v0.24.0" → "v0.24.0")
+ if "/" in version:
+ version = version.rsplit("/", 1)[-1]
tag = normalize_version(version)
- (dist_dir / ".version").write_text(f"{tag}\n")
+ module = go_module_path(source)
+ (dist_dir / ".version").write_text(f"{module}@{tag}\n")
shutil.copy(go_dir / "cli.sh", dist_dir / "cli.sh")
shutil.copy(go_dir / "otdfctl.sh", dist_dir / "otdfctl.sh")
shutil.copy(go_dir / "opentdfctl.yaml", dist_dir / "opentdfctl.yaml")
- print(f" Pre-warming Go cache for otdfctl@{tag}...")
+ print(f" Pre-warming Go cache for {module}@{tag}...")
result = subprocess.run(
- ["go", "install", f"github.com/opentdf/otdfctl@{tag}"],
+ ["go", "install", f"{module}@{tag}"],
capture_output=True,
text=True,
)
if result.returncode != 0:
- print(
- f" Warning: go install pre-warm failed (will retry at runtime): {result.stderr.strip()}"
- )
+ msg = f"go install pre-warm failed: {result.stderr.strip()}"
+ if module == GO_MODULE_PATH_PLATFORM:
+ raise InstallError(
+ f"{msg}\nThe platform module path {module}@{tag} may not be published yet."
+ )
+ print(f" Warning: {msg} (will retry at runtime)")
print(f" Go release {tag} installed to {dist_dir}")
-def install_js_release(version: str, dist_dir: Path) -> None:
+def install_js_release(version: str, dist_dir: Path, **_kwargs: object) -> None:
"""Install a JS CLI release from npm registry."""
js_dir = get_sdk_dir() / "js"
dist_dir.mkdir(parents=True, exist_ok=True)
@@ -65,7 +82,7 @@ def install_js_release(version: str, dist_dir: Path) -> None:
print(f" JS release {v} installed to {dist_dir}")
-def install_java_release(version: str, dist_dir: Path) -> None:
+def install_java_release(version: str, dist_dir: Path, **_kwargs: object) -> None:
"""Install a Java CLI release by downloading cmdline.jar from GitHub Releases.
Raises InstallError if the artifact is not available or download fails,
@@ -133,13 +150,15 @@ def install_java_release(version: str, dist_dir: Path) -> None:
}
-def install_release(sdk: str, version: str, dist_name: str | None = None) -> Path:
+def install_release(sdk: str, version: str, dist_name: str | None = None, **kwargs: object) -> Path:
"""Install a released version of an SDK CLI.
Args:
sdk: One of "go", "js", "java"
version: Version string (e.g., "v0.24.0" or "0.24.0")
dist_name: Override the dist directory name (defaults to normalized version)
+ **kwargs: Extra arguments forwarded to the SDK installer
+ (e.g., source="platform" for Go).
Returns:
Path to the created dist directory
@@ -157,26 +176,34 @@ def install_release(sdk: str, version: str, dist_name: str | None = None) -> Pat
print(f" Dist directory already exists: {dist_dir} (skipping)")
return dist_dir
- INSTALLERS[sdk](version, dist_dir)
+ INSTALLERS[sdk](version, dist_dir, **kwargs)
return dist_dir
-def latest_stable_version(sdk: str) -> str | None:
- """Find the latest stable version for an SDK that has a CLI available."""
+def latest_stable_version(sdk: str) -> tuple[str, str | None] | None:
+ """Find the latest stable version for an SDK that has a CLI available.
+
+ Returns (version, source) where source is "platform" for Go versions
+ from the platform repo, or None otherwise.
+ """
if sdk == "go":
versions = list_go_versions()
stable = [v for v in versions if v.get("stable", False)]
- return stable[-1]["version"] if stable else None
+ if not stable:
+ return None
+ entry = stable[-1]
+ source = "platform" if entry.get("source") == "platform-git-tag" else None
+ return entry["version"], source
elif sdk == "js":
versions = list_js_versions()
stable = [v for v in versions if v.get("stable", False)]
- return stable[-1]["version"] if stable else None
+ return (stable[-1]["version"], None) if stable else None
elif sdk == "java":
releases = list_java_github_releases()
stable_with_cli = [
v for v in releases if v.get("stable", False) and v.get("has_cli", False)
]
- return stable_with_cli[-1]["version"] if stable_with_cli else None
+ return (stable_with_cli[-1]["version"], None) if stable_with_cli else None
return None
@@ -184,12 +211,13 @@ def cmd_stable(sdks: list[str]) -> None:
"""Install the latest stable release for each SDK."""
for sdk in sdks:
print(f"Finding latest stable {sdk} release...")
- version = latest_stable_version(sdk)
- if version is None:
+ result = latest_stable_version(sdk)
+ if result is None:
print(f" Warning: No stable version found for {sdk}, skipping")
continue
+ version, source = result
print(f" Latest stable {sdk}: {version}")
- install_release(sdk, version)
+ install_release(sdk, version, source=source)
def cmd_lts(sdks: list[str]) -> None:
@@ -224,7 +252,9 @@ def cmd_release(specs: list[str]) -> None:
install_release(sdk, version)
-def cmd_install(sdk: str, version: str, dist_name: str | None = None) -> None:
+def cmd_install(
+ sdk: str, version: str, dist_name: str | None = None, source: str | None = None
+) -> None:
"""Install a single SDK version (used by CI action)."""
print(f"Installing {sdk} {version}...")
- install_release(sdk, version, dist_name=dist_name)
+ install_release(sdk, version, dist_name=dist_name, source=source)
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/registry.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/registry.py
index 8f8dd34e5..8d27cd68f 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/registry.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/registry.py
@@ -12,11 +12,13 @@
from typing import Any
from otdf_sdk_mgr.config import (
- GO_INSTALL_PREFIX,
+ GO_INSTALL_PREFIX_PLATFORM,
+ GO_INSTALL_PREFIX_STANDALONE,
SDK_GITHUB_REPOS,
SDK_GIT_URLS,
SDK_MAVEN_COORDS,
SDK_NPM_PACKAGES,
+ SDK_TAG_INFIXES_PLATFORM_GO,
)
from otdf_sdk_mgr.semver import is_stable, parse_semver, semver_sort_key
@@ -68,12 +70,15 @@ def fetch_text(url: str) -> str:
def list_go_versions() -> list[dict[str, Any]]:
- """List Go SDK versions from git tags."""
+ """List Go SDK versions from git tags in both standalone and platform repos."""
+ import git.exc
from git import Git
repo = Git()
+ seen: dict[str, dict[str, Any]] = {}
+
+ # Standalone repo (opentdf/otdfctl): tags like v0.24.0
raw = repo.ls_remote(SDK_GIT_URLS["go"], tags=True)
- results = []
for line in raw.strip().split("\n"):
if not line:
continue
@@ -83,16 +88,52 @@ def list_go_versions() -> list[dict[str, Any]]:
tag = ref.removeprefix("refs/tags/")
if not parse_semver(tag):
continue
- version = tag
- results.append(
- {
+ seen[tag] = {
+ "sdk": "go",
+ "version": tag,
+ "source": "git-tag",
+ "install_method": f"{GO_INSTALL_PREFIX_STANDALONE}@{tag}",
+ "stable": is_stable(tag),
+ }
+
+ # Platform repo (opentdf/platform): tags like otdfctl/v0.X.Y
+ infix = SDK_TAG_INFIXES_PLATFORM_GO
+ try:
+ raw = repo.ls_remote(SDK_GIT_URLS["platform"], tags=True)
+ for line in raw.strip().split("\n"):
+ if not line:
+ continue
+ _, ref = line.split("\t", 1)
+ if ref.endswith("^{}"):
+ continue
+ tag = ref.removeprefix("refs/tags/")
+ if not tag.startswith(f"{infix}/"):
+ continue
+ version = tag.removeprefix(f"{infix}/")
+ if not parse_semver(version):
+ continue
+ # Platform entries take precedence (canonical location post-migration);
+ # if the same version exists in both repos, the platform entry
+ # overwrites the standalone one (a notice is printed below).
+ if version in seen:
+ print(
+ f"Note: version {version} found in both standalone and platform repos; using platform source.",
+ file=sys.stderr,
+ )
+ seen[version] = {
"sdk": "go",
"version": version,
- "source": "git-tag",
- "install_method": f"{GO_INSTALL_PREFIX}@{version}",
+ "source": "platform-git-tag",
+ "install_method": f"{GO_INSTALL_PREFIX_PLATFORM}@{version}",
"stable": is_stable(version),
}
+ except git.exc.GitCommandError as e:
+ print(
+ f"::warning::Failed to query platform repo for go tags: {e}",
+ file=sys.stderr,
)
+
+ results = list(seen.values())
results.sort(key=lambda r: semver_sort_key(r["version"]))
return results
diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py
index 6e4cd7ca1..a3c6115bf 100644
--- a/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py
+++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py
@@ -5,6 +5,7 @@
import re
from typing import NotRequired, TypedDict, TypeGuard
+import git.exc
from git import Git
from otdf_sdk_mgr.config import (
@@ -12,6 +13,8 @@
LTS_VERSIONS,
SDK_GIT_URLS,
SDK_NPM_PACKAGES,
+ go_git_url,
+ go_tag_infix,
)
@@ -23,6 +26,7 @@ class ResolveSuccess(TypedDict):
pr: NotRequired[str]
release: NotRequired[str]
sha: str
+ source: NotRequired[str]
tag: str
@@ -111,78 +115,128 @@ def lookup_additional_options(sdk: str, version: str) -> str | None:
return None
-def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
- """Resolve a version spec to a concrete SHA and tag."""
+def resolve(
+ sdk: str,
+ version: str,
+ infix: str | None,
+ go_source: str | None = None,
+) -> ResolveResult:
+ """Resolve a version spec to a concrete SHA and tag.
+
+ Args:
+ sdk: SDK identifier (go, js, java, platform).
+ version: Version spec (main, SHA, tag, latest, lts, etc.).
+ infix: Tag infix for monorepo tag resolution (e.g. "sdk" for JS).
+ go_source: For sdk=="go", override the git URL and infix.
+ "platform" resolves against the platform monorepo (otdfctl/ prefix tags).
+ None or "standalone" uses the standalone otdfctl repo (default).
+ """
+ _go_platform = sdk == "go" and go_source == "platform"
+
+ def _annotate(result: ResolveResult) -> ResolveResult:
+ """Add source field to successful results when resolving go from platform.
+
+ Error results are returned unchanged.
+ """
+ if _go_platform and is_resolve_success(result):
+ result["source"] = "platform"
+ return result
+
+ # Validate config and resolve URLs before the try block so that
+ # programming errors (bad go_source, unknown SDK) propagate immediately.
+ if _go_platform:
+ sdk_url = go_git_url("platform")
+ infix = go_tag_infix("platform")
+ else:
+ try:
+ sdk_url = SDK_GIT_URLS[sdk]
+ except KeyError:
+ return {"sdk": sdk, "alias": version, "err": f"unknown SDK: {sdk}"}
+
try:
- sdk_url = SDK_GIT_URLS[sdk]
repo = Git()
if version == "main" or version == "refs/heads/main":
all_heads = [r.split("\t") for r in repo.ls_remote(sdk_url, heads=True).split("\n")]
- sha, _ = [tag for tag in all_heads if "refs/heads/main" in tag][0]
- return {
- "sdk": sdk,
- "alias": version,
- "head": True,
- "sha": sha,
- "tag": "main",
- }
+ try:
+ sha, _ = next(tag for tag in all_heads if "refs/heads/main" in tag)
+ except StopIteration:
+ return {"sdk": sdk, "alias": version, "err": f"main branch not found in {sdk_url}"}
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": True,
+ "sha": sha,
+ "tag": "main",
+ }
+ )
if re.match(SHA_REGEX, version):
ls_remote = [r.split("\t") for r in repo.ls_remote(sdk_url).split("\n")]
matching_tags = [(sha, tag) for (sha, tag) in ls_remote if sha.startswith(version)]
if not matching_tags:
- return {
- "sdk": sdk,
- "alias": version[:7],
- "sha": version,
- "tag": version,
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version[:7],
+ "sha": version,
+ "tag": version,
+ }
+ )
if len(matching_tags) > 1:
for sha, tag in matching_tags:
if tag.startswith("refs/pull/"):
pr_number = tag.split("/")[2]
- return {
- "sdk": sdk,
- "alias": version,
- "head": True,
- "sha": sha,
- "tag": f"pull-{pr_number}",
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": True,
+ "sha": sha,
+ "tag": f"pull-{pr_number}",
+ }
+ )
for sha, tag in matching_tags:
mq_match = re.match(MERGE_QUEUE_REGEX, tag)
if mq_match:
to_branch = mq_match.group("branch")
pr_number = mq_match.group("pr_number")
if to_branch and pr_number:
- return {
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": True,
+ "pr": pr_number,
+ "sha": sha,
+ "tag": f"mq-{to_branch}-{pr_number}",
+ }
+ )
+ suffix = tag.split("refs/heads/gh-readonly-queue/")[-1]
+ flattag = "mq--" + suffix.replace("/", "--")
+ return _annotate(
+ {
"sdk": sdk,
"alias": version,
"head": True,
- "pr": pr_number,
"sha": sha,
- "tag": f"mq-{to_branch}-{pr_number}",
+ "tag": flattag,
}
- suffix = tag.split("refs/heads/gh-readonly-queue/")[-1]
- flattag = "mq--" + suffix.replace("/", "--")
- return {
- "sdk": sdk,
- "alias": version,
- "head": True,
- "sha": sha,
- "tag": flattag,
- }
+ )
head = False
if tag.startswith("refs/heads/"):
head = True
tag = tag.split("refs/heads/")[-1]
flattag = tag.replace("/", "--")
- return {
- "sdk": sdk,
- "alias": version,
- "head": head,
- "sha": sha,
- "tag": flattag,
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": head,
+ "sha": sha,
+ "tag": flattag,
+ }
+ )
return {
"sdk": sdk,
@@ -197,12 +251,14 @@ def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
tag = tag.split("refs/tags/")[-1]
if infix:
tag = tag.split(f"{infix}/")[-1]
- return {
- "sdk": sdk,
- "alias": version,
- "sha": sha,
- "tag": tag,
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "sha": sha,
+ "tag": tag,
+ }
+ )
if version.startswith("refs/pull/"):
merge_heads = [
@@ -216,14 +272,16 @@ def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
"err": f"pull request {pr_number} not found in {sdk_url}",
}
sha, _ = merge_heads[0]
- return {
- "sdk": sdk,
- "alias": version,
- "head": True,
- "pr": pr_number,
- "sha": sha,
- "tag": f"pull-{pr_number}",
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": True,
+ "pr": pr_number,
+ "sha": sha,
+ "tag": f"pull-{pr_number}",
+ }
+ )
remote_tags = [r.split("\t") for r in repo.ls_remote(sdk_url).split("\n")]
all_listed_tags = [
@@ -238,13 +296,15 @@ def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
if version in all_listed_branches:
sha = all_listed_branches[version]
- return {
- "sdk": sdk,
- "alias": version,
- "head": True,
- "sha": sha,
- "tag": version,
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": version,
+ "head": True,
+ "sha": sha,
+ "tag": version,
+ }
+ )
if infix and version.startswith(f"{infix}/"):
version = version.split(f"{infix}/")[-1]
@@ -288,13 +348,15 @@ def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
if not matching_tags:
# No versions with CLI found, fall back to building latest from source
sha, tag = stable_tags[-1]
- return {
- "sdk": sdk,
- "alias": alias,
- "head": True, # Mark as head to trigger source checkout
- "sha": sha,
- "tag": tag,
- }
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": alias,
+ "head": True, # Mark as head to trigger source checkout
+ "sha": sha,
+ "tag": tag,
+ }
+ )
else:
matching_tags = stable_tags[-1:]
else:
@@ -319,14 +381,16 @@ def resolve(sdk: str, version: str, infix: str | None) -> ResolveResult:
release = tag
if infix:
release = f"{infix}/{release}"
- return {
- "sdk": sdk,
- "alias": alias,
- "release": release,
- "sha": sha,
- "tag": tag,
- }
- except Exception as e:
+ return _annotate(
+ {
+ "sdk": sdk,
+ "alias": alias,
+ "release": release,
+ "sha": sha,
+ "tag": tag,
+ }
+ )
+ except (git.exc.GitCommandError, ValueError) as e:
return {
"sdk": sdk,
"alias": version,
diff --git a/xtest/sdk/go/cli.sh b/xtest/sdk/go/cli.sh
index 172aa5b50..75b08728b 100755
--- a/xtest/sdk/go/cli.sh
+++ b/xtest/sdk/go/cli.sh
@@ -21,10 +21,16 @@
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
cmd=("$SCRIPT_DIR"/otdfctl)
-if [ ! -f "$SCRIPT_DIR"/otdfctl ]; then
- if [ -f "$SCRIPT_DIR/.version" ]; then
- OTDFCTL_VERSION=$(tr -d '[:space:]' <"$SCRIPT_DIR/.version")
- cmd=(go run "github.com/opentdf/otdfctl@${OTDFCTL_VERSION}")
+if [[ ! -f "$SCRIPT_DIR"/otdfctl ]]; then
+ if [[ -f "$SCRIPT_DIR/.version" ]]; then
+ VERSION_SPEC=$(tr -d '[:space:]' <"$SCRIPT_DIR/.version")
+ if [[ "$VERSION_SPEC" == *@* ]]; then
+ # New format: module-path@version
+ cmd=(go run "$VERSION_SPEC")
+ else
+ # Legacy format: bare version tag, default to standalone module
+ cmd=(go run "github.com/opentdf/otdfctl@${VERSION_SPEC}")
+ fi
else
cmd=(go run "github.com/opentdf/otdfctl@latest")
fi
diff --git a/xtest/sdk/go/otdfctl.sh b/xtest/sdk/go/otdfctl.sh
index 17fbb0c84..c30ab481c 100755
--- a/xtest/sdk/go/otdfctl.sh
+++ b/xtest/sdk/go/otdfctl.sh
@@ -16,10 +16,16 @@ done
source "$XTEST_DIR/test.env"
cmd=("$SCRIPT_DIR"/otdfctl)
-if [ ! -f "$SCRIPT_DIR"/otdfctl ]; then
- if [ -f "$SCRIPT_DIR/.version" ]; then
- OTDFCTL_VERSION=$(tr -d '[:space:]' <"$SCRIPT_DIR/.version")
- cmd=(go run "github.com/opentdf/otdfctl@${OTDFCTL_VERSION}")
+if [[ ! -f "$SCRIPT_DIR"/otdfctl ]]; then
+ if [[ -f "$SCRIPT_DIR/.version" ]]; then
+ VERSION_SPEC=$(tr -d '[:space:]' <"$SCRIPT_DIR/.version")
+ if [[ "$VERSION_SPEC" == *@* ]]; then
+ # New format: module-path@version
+ cmd=(go run "$VERSION_SPEC")
+ else
+ # Legacy format: bare version tag, default to standalone module
+ cmd=(go run "github.com/opentdf/otdfctl@${VERSION_SPEC}")
+ fi
else
cmd=(go run "github.com/opentdf/otdfctl@latest")
fi
diff --git a/xtest/setup-cli-tool/action.yaml b/xtest/setup-cli-tool/action.yaml
index 9e110ef4f..3cb5e95a9 100644
--- a/xtest/setup-cli-tool/action.yaml
+++ b/xtest/setup-cli-tool/action.yaml
@@ -2,12 +2,23 @@ name: configure-sdks
description: Check out and build one or more SDK and its CLI tool for use by xtest
inputs:
path:
- description: The path to checkout the the SDK source code to; concatenated with branch or tag name.
+ description: The path to check out the SDK source code to; concatenated with branch or tag name.
sdk:
description: The SDK to configure; one of go, java, js
version-info:
description: JSON-encoded output of otdf-sdk-mgr versions resolve
required: true
+ platform-otdfctl-dir:
+ description: >-
+ Absolute path to platform's otdfctl/ directory. When set and sdk is "go",
+ head versions whose SHA matches platform-otdfctl-sha are symlinked from
+ here instead of checked out separately. Used in both explicit platform
+ mode (source: "platform" in resolved version) and auto-detect mode.
+ platform-otdfctl-sha:
+ description: >-
+ SHA of the commit that platform-otdfctl-dir was checked out at.
+ Used to decide which Go head version (if any) can reuse the existing
+ platform checkout vs needing a fresh one.
outputs:
version-a:
description: "Object containing tag, sha, and name of a version checked out"
@@ -28,24 +39,27 @@ outputs:
runs:
using: composite
steps:
- - name: identify repo url
+ - name: identify repo urls
shell: bash
run: |
- case "${{ inputs.sdk }}" in
+ case "$INPUT_SDK" in
"go")
- echo "sdk_repo=opentdf/otdfctl" >> $GITHUB_ENV
+ echo "STANDALONE_REPO=opentdf/otdfctl" >> "$GITHUB_ENV"
;;
"java")
- echo "sdk_repo=opentdf/java-sdk" >> $GITHUB_ENV
+ echo "STANDALONE_REPO=opentdf/java-sdk" >> "$GITHUB_ENV"
;;
"js")
- echo "sdk_repo=opentdf/web-sdk" >> $GITHUB_ENV
+ echo "STANDALONE_REPO=opentdf/web-sdk" >> "$GITHUB_ENV"
;;
*)
- echo "Invalid SDK specified: ${{ inputs.sdk }}" >> $GITHUB_STEP_SUMMARY
+ echo "Invalid SDK specified: $INPUT_SDK" >> "$GITHUB_STEP_SUMMARY"
exit 1
;;
esac
+ echo "PLATFORM_REPO=opentdf/platform" >> "$GITHUB_ENV"
+ env:
+ INPUT_SDK: ${{ inputs.sdk }}
- name: resolve versions
id: resolve
@@ -57,10 +71,10 @@ runs:
fi
if echo "$version_info" | jq -e '.[] | select(.err != null)' > /dev/null; then
- echo "Error: One or more errors occurred while resolving versions for ${{ inputs.sdk }}: $version_info"
+ echo "Error: One or more errors occurred while resolving versions for $INPUT_SDK: $version_info"
exit 1
fi
- echo "Version Info for ${{ inputs.sdk }}: $version_info"
+ echo "Version Info for $INPUT_SDK: $version_info"
version_a=$(echo "$version_info" | jq -rc '.[0] // empty')
version_b=$(echo "$version_info" | jq -rc '.[1] // empty')
@@ -75,23 +89,27 @@ runs:
echo "heads=$head_tags" >> $GITHUB_OUTPUT
env:
version_info: ${{ inputs.version-info }}
+ INPUT_SDK: ${{ inputs.sdk }}
- name: install released versions
shell: bash
run: |
- SDK_MGR_DIR="$(cd "${{ inputs.path }}/../.." && pwd)/otdf-sdk-mgr"
+ SDK_MGR_DIR="$(cd "$INPUT_PATH/../.." && pwd)/otdf-sdk-mgr"
echo "${version_info}" | jq -c '.[]' | while IFS= read -r row; do
tag=$(echo "$row" | jq -r '.tag')
head=$(echo "$row" | jq -r '.head // false')
release=$(echo "$row" | jq -r '.release // empty')
if [[ "$head" != "true" && -n "$release" ]]; then
- echo "Installing ${{ inputs.sdk }} $tag from registry (release: $release)"
+ echo "Installing $INPUT_SDK $tag from registry (release: $release)"
# Sanitize tag for use as an env var name (replace non-alphanumeric/underscore with _)
tag_sanitized="${tag//[^a-zA-Z0-9_]/_}"
+ source=$(echo "$row" | jq -r '.source // empty')
+ source_args=()
+ [[ -n "$source" ]] && source_args=(--source "$source")
if ! uv run --project "$SDK_MGR_DIR" otdf-sdk-mgr install artifact \
- --sdk "${{ inputs.sdk }}" --version "$release" \
- --dist-name "$tag"; then
- echo " Warning: Artifact installation failed for ${{ inputs.sdk }} $tag"
+ --sdk "$INPUT_SDK" --version "$release" \
+ --dist-name "$tag" "${source_args[@]}"; then
+ echo " Warning: Artifact installation failed for $INPUT_SDK $tag"
echo " Will fall back to building from source"
echo "BUILD_FROM_SOURCE_${tag_sanitized}=true" >> "$GITHUB_ENV"
fi
@@ -99,14 +117,17 @@ runs:
done
env:
version_info: ${{ inputs.version-info }}
+ INPUT_PATH: ${{ inputs.path }}
+ INPUT_SDK: ${{ inputs.sdk }}
- name: determine source checkout needs
id: check-source
shell: bash
run: |
- # Determine which version slots need source checkout.
- # A slot needs checkout if it is a head version OR if artifact install failed
- # (BUILD_FROM_SOURCE_ was set in the previous step).
+ # Determine which version slots need source checkout and from which repo.
+ # A slot needs checkout if it is a head version OR if artifact install failed.
+ # Platform-source versions may reuse the existing platform-otdfctl-dir
+ # (when their SHA matches) or need a fresh opentdf/platform checkout.
for slot in a b c d; do
case "$slot" in
a) row=$(echo "${version_info}" | jq -rc '.[0] // empty') ;;
@@ -115,72 +136,209 @@ runs:
d) row=$(echo "${version_info}" | jq -rc '.[3] // empty') ;;
esac
if [[ -z "$row" ]]; then
- echo "needs-source-${slot}=false" >> "$GITHUB_OUTPUT"
+ echo "needs-checkout-${slot}=false" >> "$GITHUB_OUTPUT"
+ echo "is-platform-${slot}=false" >> "$GITHUB_OUTPUT"
+ echo "use-existing-platform-dir-${slot}=false" >> "$GITHUB_OUTPUT"
+ echo "checkout-repo-${slot}=" >> "$GITHUB_OUTPUT"
+ echo "checkout-path-${slot}=" >> "$GITHUB_OUTPUT"
continue
fi
+
tag=$(echo "$row" | jq -r '.tag')
head=$(echo "$row" | jq -r '.head // false')
+ sha=$(echo "$row" | jq -r '.sha')
+ source=$(echo "$row" | jq -r '.source // empty')
tag_sanitized="${tag//[^a-zA-Z0-9_]/_}"
build_from_source_var="BUILD_FROM_SOURCE_${tag_sanitized}"
+ needs_source=false
if [[ "$head" == "true" || "${!build_from_source_var}" == "true" ]]; then
- echo "needs-source-${slot}=true" >> "$GITHUB_OUTPUT"
- else
- echo "needs-source-${slot}=false" >> "$GITHUB_OUTPUT"
+ needs_source=true
+ fi
+
+ is_platform=false
+ use_existing=false
+ checkout_repo="$STANDALONE_REPO"
+ checkout_path="${INPUT_PATH}/${INPUT_SDK}/src/${tag}"
+
+ if [[ "$source" == "platform" ]]; then
+ # Explicit platform mode: resolver tagged this version as from
+ # opentdf/platform. Use per-version SHA to decide checkout strategy.
+ is_platform=true
+ if [[ "$needs_source" == "true" && -n "$PLATFORM_OTDFCTL_DIR" \
+ && -n "$PLATFORM_OTDFCTL_SHA" && "$sha" == "$PLATFORM_OTDFCTL_SHA" ]]; then
+ # SHA matches existing platform checkout — reuse via symlink
+ use_existing=true
+ needs_source=false
+ elif [[ "$needs_source" == "true" ]]; then
+ # Different SHA — need a fresh platform checkout
+ checkout_repo="$PLATFORM_REPO"
+ checkout_path="${INPUT_PATH}/${INPUT_SDK}/platform-src/${tag}"
+ fi
+ elif [[ "$INPUT_SDK" == "go" && -n "$PLATFORM_OTDFCTL_DIR" && "$needs_source" == "true" ]]; then
+ # Auto-detect fallback: resolver used standalone repo but the
+ # test job detected otdfctl in the platform checkout.
+ # NOTE: SHA comparison across repos is not meaningful (the standalone
+ # repo and platform repo have different commit histories), so we
+ # cannot safely reuse the platform checkout here. Fall through to
+ # a standalone checkout. To use the platform source, set
+ # otdfctl-source=platform explicitly.
+ echo "::notice::Go version ${tag} resolved from standalone repo; platform checkout available but cannot auto-reuse (different repo). Set otdfctl-source=platform to use the platform source."
fi
+
+ echo "needs-checkout-${slot}=${needs_source}" >> "$GITHUB_OUTPUT"
+ echo "is-platform-${slot}=${is_platform}" >> "$GITHUB_OUTPUT"
+ echo "use-existing-platform-dir-${slot}=${use_existing}" >> "$GITHUB_OUTPUT"
+ echo "checkout-repo-${slot}=${checkout_repo}" >> "$GITHUB_OUTPUT"
+ echo "checkout-path-${slot}=${checkout_path}" >> "$GITHUB_OUTPUT"
done
env:
version_info: ${{ inputs.version-info }}
+ INPUT_PATH: ${{ inputs.path }}
+ INPUT_SDK: ${{ inputs.sdk }}
+ PLATFORM_OTDFCTL_DIR: ${{ inputs.platform-otdfctl-dir }}
+ PLATFORM_OTDFCTL_SHA: ${{ inputs.platform-otdfctl-sha }}
+
+ - name: symlink existing platform checkout
+ shell: bash
+ run: |
+ # For versions that can reuse the already-checked-out platform dir,
+ # symlink platform-otdfctl-dir into sdk/go/src/{tag}.
+ for slot in a b c d; do
+ case "$slot" in
+ a) version_json="$VERSION_A" ; use_existing="$USE_EXISTING_A" ;;
+ b) version_json="$VERSION_B" ; use_existing="$USE_EXISTING_B" ;;
+ c) version_json="$VERSION_C" ; use_existing="$USE_EXISTING_C" ;;
+ d) version_json="$VERSION_D" ; use_existing="$USE_EXISTING_D" ;;
+ esac
+ if [[ -z "$version_json" || "$use_existing" != "true" ]]; then
+ continue
+ fi
+ tag=$(echo "$version_json" | jq -r '.tag')
+ src_dir="${INPUT_PATH}/${INPUT_SDK}/src/${tag}"
+ echo "Symlinking existing platform otdfctl to ${src_dir}"
+ mkdir -p "$(dirname "$src_dir")"
+ ln -sfn "$PLATFORM_OTDFCTL_DIR" "$src_dir"
+ if [ ! -e "$src_dir" ]; then
+ echo "::error::Symlink target does not exist: $PLATFORM_OTDFCTL_DIR"
+ exit 1
+ fi
+ done
+ env:
+ PLATFORM_OTDFCTL_DIR: ${{ inputs.platform-otdfctl-dir }}
+ INPUT_PATH: ${{ inputs.path }}
+ INPUT_SDK: ${{ inputs.sdk }}
+ VERSION_A: ${{ steps.resolve.outputs.version-a }}
+ VERSION_B: ${{ steps.resolve.outputs.version-b }}
+ VERSION_C: ${{ steps.resolve.outputs.version-c }}
+ VERSION_D: ${{ steps.resolve.outputs.version-d }}
+ USE_EXISTING_A: ${{ steps.check-source.outputs.use-existing-platform-dir-a }}
+ USE_EXISTING_B: ${{ steps.check-source.outputs.use-existing-platform-dir-b }}
+ USE_EXISTING_C: ${{ steps.check-source.outputs.use-existing-platform-dir-c }}
+ USE_EXISTING_D: ${{ steps.check-source.outputs.use-existing-platform-dir-d }}
- name: checkout version a
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: >-
steps.resolve.outputs.version-a != ''
- && steps.check-source.outputs.needs-source-a == 'true'
+ && steps.check-source.outputs.needs-checkout-a == 'true'
with:
- path: ${{ inputs.path }}/${{ inputs.sdk }}/src/${{ fromJson(steps.resolve.outputs.version-a).tag }}
+ path: ${{ steps.check-source.outputs.checkout-path-a }}
persist-credentials: false
ref: ${{ fromJson(steps.resolve.outputs.version-a).sha }}
- repository: ${{ env.sdk_repo }}
+ repository: ${{ steps.check-source.outputs.checkout-repo-a }}
- name: checkout version b
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: >-
steps.resolve.outputs.version-b != ''
- && steps.check-source.outputs.needs-source-b == 'true'
+ && steps.check-source.outputs.needs-checkout-b == 'true'
with:
- path: ${{ inputs.path }}/${{ inputs.sdk }}/src/${{ fromJson(steps.resolve.outputs.version-b).tag }}
+ path: ${{ steps.check-source.outputs.checkout-path-b }}
persist-credentials: false
ref: ${{ fromJson(steps.resolve.outputs.version-b).sha }}
- repository: ${{ env.sdk_repo }}
+ repository: ${{ steps.check-source.outputs.checkout-repo-b }}
- name: checkout version c
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: >-
steps.resolve.outputs.version-c != ''
- && steps.check-source.outputs.needs-source-c == 'true'
+ && steps.check-source.outputs.needs-checkout-c == 'true'
with:
- path: ${{ inputs.path }}/${{ inputs.sdk }}/src/${{ fromJson(steps.resolve.outputs.version-c).tag }}
+ path: ${{ steps.check-source.outputs.checkout-path-c }}
persist-credentials: false
ref: ${{ fromJson(steps.resolve.outputs.version-c).sha }}
- repository: ${{ env.sdk_repo }}
+ repository: ${{ steps.check-source.outputs.checkout-repo-c }}
- name: checkout version d
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: >-
steps.resolve.outputs.version-d != ''
- && steps.check-source.outputs.needs-source-d == 'true'
+ && steps.check-source.outputs.needs-checkout-d == 'true'
with:
- path: ${{ inputs.path }}/${{ inputs.sdk }}/src/${{ fromJson(steps.resolve.outputs.version-d).tag }}
+ path: ${{ steps.check-source.outputs.checkout-path-d }}
persist-credentials: false
ref: ${{ fromJson(steps.resolve.outputs.version-d).sha }}
- repository: ${{ env.sdk_repo }}
+ repository: ${{ steps.check-source.outputs.checkout-repo-d }}
+
+ - name: symlink freshly checked-out platform sources
+ shell: bash
+ run: |
+ # For platform-source versions that were checked out (not reusing the
+ # existing dir), symlink {platform-src}/{tag}/otdfctl → src/{tag} so
+ # the Makefile discovers them.
+ for slot in a b c d; do
+ case "$slot" in
+ a) is_platform="$IS_PLATFORM_A" ; needs_checkout="$NEEDS_CHECKOUT_A"
+ checkout_path="$CHECKOUT_PATH_A" ; version_json="$VERSION_A" ;;
+ b) is_platform="$IS_PLATFORM_B" ; needs_checkout="$NEEDS_CHECKOUT_B"
+ checkout_path="$CHECKOUT_PATH_B" ; version_json="$VERSION_B" ;;
+ c) is_platform="$IS_PLATFORM_C" ; needs_checkout="$NEEDS_CHECKOUT_C"
+ checkout_path="$CHECKOUT_PATH_C" ; version_json="$VERSION_C" ;;
+ d) is_platform="$IS_PLATFORM_D" ; needs_checkout="$NEEDS_CHECKOUT_D"
+ checkout_path="$CHECKOUT_PATH_D" ; version_json="$VERSION_D" ;;
+ esac
+ if [[ "$is_platform" != "true" || "$needs_checkout" != "true" || -z "$version_json" ]]; then
+ continue
+ fi
+ tag=$(echo "$version_json" | jq -r '.tag')
+ src_dir="${INPUT_PATH}/${INPUT_SDK}/src/${tag}"
+ otdfctl_dir="$(cd "${checkout_path}/otdfctl" && pwd)"
+ echo "Symlinking freshly checked-out platform otdfctl ${otdfctl_dir} → ${src_dir}"
+ mkdir -p "$(dirname "$src_dir")"
+ ln -sfn "$otdfctl_dir" "$src_dir"
+ if [ ! -e "$src_dir" ]; then
+ echo "::error::Symlink target does not exist: ${otdfctl_dir} (does the platform repo contain an otdfctl/ directory?)"
+ exit 1
+ fi
+ done
+ env:
+ INPUT_PATH: ${{ inputs.path }}
+ INPUT_SDK: ${{ inputs.sdk }}
+ VERSION_A: ${{ steps.resolve.outputs.version-a }}
+ VERSION_B: ${{ steps.resolve.outputs.version-b }}
+ VERSION_C: ${{ steps.resolve.outputs.version-c }}
+ VERSION_D: ${{ steps.resolve.outputs.version-d }}
+ IS_PLATFORM_A: ${{ steps.check-source.outputs.is-platform-a }}
+ IS_PLATFORM_B: ${{ steps.check-source.outputs.is-platform-b }}
+ IS_PLATFORM_C: ${{ steps.check-source.outputs.is-platform-c }}
+ IS_PLATFORM_D: ${{ steps.check-source.outputs.is-platform-d }}
+ NEEDS_CHECKOUT_A: ${{ steps.check-source.outputs.needs-checkout-a }}
+ NEEDS_CHECKOUT_B: ${{ steps.check-source.outputs.needs-checkout-b }}
+ NEEDS_CHECKOUT_C: ${{ steps.check-source.outputs.needs-checkout-c }}
+ NEEDS_CHECKOUT_D: ${{ steps.check-source.outputs.needs-checkout-d }}
+ CHECKOUT_PATH_A: ${{ steps.check-source.outputs.checkout-path-a }}
+ CHECKOUT_PATH_B: ${{ steps.check-source.outputs.checkout-path-b }}
+ CHECKOUT_PATH_C: ${{ steps.check-source.outputs.checkout-path-c }}
+ CHECKOUT_PATH_D: ${{ steps.check-source.outputs.checkout-path-d }}
- name: post checkout cleanups
if: inputs.sdk == 'java'
run: |
- SDK_MGR_DIR="$(cd "${{ inputs.path }}/../.." && pwd)/otdf-sdk-mgr"
+ SDK_MGR_DIR="$(cd "$INPUT_PATH/../.." && pwd)/otdf-sdk-mgr"
uv run --project "$SDK_MGR_DIR" otdf-sdk-mgr java-fixup
shell: bash
+ env:
+ INPUT_PATH: ${{ inputs.path }}
- name: save env from version-info
shell: bash
@@ -190,8 +348,10 @@ runs:
tag=$(echo "$version" | jq -r '.tag')
env=$(echo "$version" | jq -r '.env // empty')
if [[ -n "$env" ]]; then
- echo "$env" > "${{ inputs.path }}/${{ inputs.sdk }}/src/${tag}.env"
+ echo "$env" > "$INPUT_PATH/$INPUT_SDK/src/${tag}.env"
fi
done
env:
version_info: ${{ inputs.version-info }}
+ INPUT_PATH: ${{ inputs.path }}
+ INPUT_SDK: ${{ inputs.sdk }}