Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
aafff16
feat(xtest): support platform-embedded otdfctl for migration to monorepo
dmihalcik-virtru Apr 15, 2026
a0fee17
fixup ruff format
dmihalcik-virtru Apr 16, 2026
7ca919a
fix(xtest): update remaining otdfctl references for platform monorepo…
dmihalcik-virtru Apr 16, 2026
d30514d
fixup pkg.go removes tag prefixes IIRC
dmihalcik-virtru Apr 16, 2026
b0e0df2
refactor(xtest): consolidate .version and .module-path into single .v…
dmihalcik-virtru Apr 16, 2026
9e1e43e
feat(sdk-mgr): wire --source option through install artifact command
dmihalcik-virtru Apr 16, 2026
e435931
fix(setup-cli-tool): avoid script injection by using env vars for inp…
dmihalcik-virtru Apr 16, 2026
199e71f
Apply suggestion from @gemini-code-assist[bot]
dmihalcik-virtru Apr 16, 2026
38a3fb3
feat(setup-cli-tool): support multiple platform-source Go versions
dmihalcik-virtru Apr 16, 2026
38a3e82
fix: address PR review findings for platform otdfctl migration
dmihalcik-virtru Apr 16, 2026
be80bfb
fix(sdk-mgr): accept "standalone" as valid Go source in go_module_path
dmihalcik-virtru Apr 16, 2026
165e2d1
docs(xtest): clarify auto mode resolves releases from standalone
dmihalcik-virtru Apr 16, 2026
4790e3c
fix(setup-cli-tool): require SHA match in auto-detect platform fallback
dmihalcik-virtru Apr 16, 2026
51edb87
fix: harden validation and error handling for platform otdfctl migration
dmihalcik-virtru Apr 16, 2026
cf4c44c
fixup ruff format
dmihalcik-virtru Apr 16, 2026
7c4db05
fix: address PR review findings for platform otdfctl migration
dmihalcik-virtru Apr 20, 2026
91953e8
fixup pytest fail
dmihalcik-virtru Apr 20, 2026
ca7b39f
Merge branch 'main' into DSPX-2655-migrate-otdfctl
alkalescent Apr 22, 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
78 changes: 71 additions & 7 deletions .github/workflows/xtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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 != '' }}
Expand Down Expand Up @@ -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') {
Expand All @@ -182,7 +199,10 @@ jobs:
return `<a href="${htmlEscape(url)}">Maven Central</a>`;
}
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 `<a href="${htmlEscape(url)}">pkg.go.dev</a>`;
}
return '';
Expand All @@ -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 = `<a href="${htmlEscape(sdkRepoUrl)}">${htmlEscape(sdkType)}</a>`;
const commitLink = sha ? `<a href="${htmlEscape(`${sdkRepoUrl}/commit/${encodeURIComponent(sha)}`)}">${htmlEscape(sha.substring(0, 7))}</a>` : ' . ';
const tagLink = (release && tag)
? `<a href="${htmlEscape(`${sdkRepoUrl}/releases/tag/${release}`)}">${htmlEscape(tag)}</a>`
: 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;
Expand Down Expand Up @@ -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:-<empty>}/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
Expand Down Expand Up @@ -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
Expand All @@ -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: >-

@alkalescent alkalescent Apr 16, 2026

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.

So in default auto mode, detect-otdfctl reports platform if the otdfctl dir exists. However, resolve-versions still pulls the Go head from the standalone repo (which skips the replace step and the build ends with the published platform modules). Should this condition be on the per-version source from resolve-versions instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't think so, it will check out a new copy of the platform repo at that tag so the go.work should be correct. I could be wrong; there are a lot of moving parts

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"
Expand Down
6 changes: 4 additions & 2 deletions otdf-sdk-mgr/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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 <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
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)

# 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:
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