Skip to content
Merged
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
105 changes: 26 additions & 79 deletions .github/workflows/xtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ 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 @@ -55,10 +50,6 @@ 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 @@ -88,14 +79,6 @@ 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 @@ -322,41 +305,29 @@ 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) || {
######## CAPTURE PLATFORM-EMBEDDED OTDFCTL LOCATION #############
# If this matrix's platform checkout contains otdfctl/ (post-v0.31.0
# tag or main), the setup-cli-tool action reuses it via symlink when an
# otdfctl version resolves to the same SHA. For older platform tags
# without otdfctl/, both outputs stay empty and setup-cli-tool does a
# fresh opentdf/platform checkout for each go head version.
- name: Capture platform otdfctl location
id: platform-otdfctl
run: |-
if [ -d "$PLATFORM_DIR/otdfctl" ] && [ -f "$PLATFORM_DIR/otdfctl/go.mod" ]; then
echo "dir=$(pwd)/$PLATFORM_DIR/otdfctl" >> "$GITHUB_OUTPUT"
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"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
echo "Reusable platform-embedded otdfctl at $PLATFORM_DIR/otdfctl ($sha)"
else
echo "otdfctl-source=standalone" >> "$GITHUB_OUTPUT"
echo "Platform checkout at $PLATFORM_DIR has no otdfctl/; each go head will get a fresh platform checkout."
echo "dir=" >> "$GITHUB_OUTPUT"
echo "sha=" >> "$GITHUB_OUTPUT"
fi
env:
OTDFCTL_SOURCE_INPUT: ${{ inputs.otdfctl-source }}
PLATFORM_DIR: ${{ steps.run-platform.outputs.platform-working-dir }}

######### CHECKOUT JS CLI #############
Expand Down Expand Up @@ -393,8 +364,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 }}
platform-otdfctl-dir: ${{ steps.platform-otdfctl.outputs.dir }}
platform-otdfctl-sha: ${{ steps.platform-otdfctl.outputs.sha }}

- name: Cache Go modules
if: fromJson(steps.configure-go.outputs.heads)[0] != null
Expand All @@ -407,38 +378,14 @@ jobs:
restore-keys: |
go-${{ runner.os }}-

- name: Resolve otdfctl heads
id: resolve-otdfctl-heads
if: fromJson(steps.configure-go.outputs.heads)[0] != null
run: |-
echo "OTDFCTL_HEADS=$OTDFCTL_HEADS" >> "$GITHUB_ENV"
env:
OTDFCTL_HEADS: ${{ steps.configure-go.outputs.heads }}

- name: Replace otdfctl go.mod packages, but only at head version of platform
if: >-
steps.detect-otdfctl.outputs.otdfctl-source != 'platform'
&& fromJson(steps.configure-go.outputs.heads)[0] != null
&& startsWith(matrix.sdk-version, '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 (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"
echo "OTDFCTL_DIR_ABS: $OTDFCTL_DIR_ABS"
for head in $(echo "${OTDFCTL_HEADS}" | jq -r '.[]'); do
echo "Processing head: $head"
cd "${OTDFCTL_DIR_ABS}/$head"
for m in lib/fixtures lib/ocrypto protocol/go sdk; do
go mod edit -replace "github.com/opentdf/platform/$m=${PLATFORM_DIR_ABS}/$m"
done
go mod tidy
done

######## SETUP THE GO CLI #############
# otdfctl head builds need the platform's go.work in scope so
# protocol/go, sdk, and lib/* resolve to the platform checkout's local
# sources (otherwise the otdfctl go.mod's pinned release versions are
# used, missing any same-PR additions). The Makefile auto-detects
# GOWORK by looking for a go.work file alongside each src/{tag} symlink
# target — works for both the platform-embedded reuse case and the
# fresh platform-src/{tag} checkout case.
- name: Prepare go cli
if: fromJson(steps.configure-go.outputs.heads)[0] != null
run: |-
Expand Down
4 changes: 2 additions & 2 deletions otdf-sdk-mgr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ otdf-sdk-mgr java-fixup

## How Release Installs Work

- **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.
- **Go**: Writes a `.version` file containing `module-path@version`; `cli.sh`/`otdfctl.sh` use `go run <module>@<version>` (no local compilation needed, Go caches the binary). The resolver always targets the opentdf/platform monorepo (`github.com/opentdf/platform/otdfctl`) for v0.31.0 and newer; pre-v0.31.0 tags fall back to the archived standalone repo (`github.com/opentdf/otdfctl`) for artifact install via the Go module proxy. Bare semver inputs like `v0.32.0` are auto-prepended with `otdfctl/` when looking up platform tags.
- **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`

## Source Builds

Source builds (`tip` mode) check out source to `sdk/{lang}/src/` and compile via `make` to `sdk/{lang}/dist/`.
Source builds (`tip` mode) check out source to `sdk/{lang}/src/` and compile via `make` to `sdk/{lang}/dist/`. For Go, the platform monorepo is cloned to `sdk/go/platform-src/{ref}/` and `sdk/go/src/{ref}` is a symlink to its `otdfctl/` subdirectory; `make` discovers the platform's top-level `go.work` automatically so `protocol/go`, `sdk`, and `lib/*` resolve to the local checkout.

After changes to SDK source, rebuild:

Expand Down
78 changes: 77 additions & 1 deletion otdf-sdk-mgr/src/otdf_sdk_mgr/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from __future__ import annotations

import shutil
import subprocess
from pathlib import Path
from typing import Any

from otdf_sdk_mgr.config import SDK_BARE_REPOS, SDK_GIT_URLS, get_sdk_dirs
Expand All @@ -16,7 +18,18 @@ def _run(cmd: list[str], **kwargs: Any) -> None:


def checkout_sdk_branch(language: str, branch: str) -> None:
"""Clone bare repo and create/update a worktree for the given branch."""
"""Clone bare repo and create/update a worktree for the given branch.

For "go", use checkout_go_from_platform instead — the standalone
opentdf/otdfctl repo is archived; otdfctl source builds come from the
platform monorepo.
"""
if language == "go":
raise ValueError(
"checkout_sdk_branch does not support 'go'; use checkout_go_from_platform "
"(otdfctl is now sourced from the opentdf/platform monorepo)."
)

sdk_dirs = get_sdk_dirs()
if language not in sdk_dirs:
raise ValueError(
Expand Down Expand Up @@ -56,3 +69,66 @@ def checkout_sdk_branch(language: str, branch: str) -> None:
branch,
]
)


def checkout_go_from_platform(ref: str) -> Path:
"""Check out the opentdf/platform monorepo and arrange xtest/sdk/go/src/<ref>
as a symlink to the otdfctl subdir of that checkout.

Returns the platform checkout directory so callers can locate go.work for
GOWORK-based source builds.
"""
go_dir = get_sdk_dirs()["go"]
platform_url = SDK_GIT_URLS["platform"].removesuffix(".git")
platform_src_dir = go_dir / "platform-src"
bare_repo_path = platform_src_dir / "platform.git"

local_name = ref.replace("/", "--").removeprefix("otdfctl--")
worktree_path = platform_src_dir / local_name
src_link = go_dir / "src" / local_name

platform_src_dir.mkdir(parents=True, exist_ok=True)

if not bare_repo_path.exists():
print(f"Cloning {platform_url} as a bare repository into {bare_repo_path}...")
_run(["git", "clone", "--bare", platform_url, str(bare_repo_path)])
else:
print(f"Bare repository already exists at {bare_repo_path}. Fetching updates...")
_run(["git", f"--git-dir={bare_repo_path}", "fetch", "--all"])

if worktree_path.exists():
print(f"Worktree for ref '{ref}' already exists at {worktree_path}. Updating...")
_run(["git", f"--git-dir={bare_repo_path}", "fetch", "origin", ref, "--tags"])
_run(["git", "-C", str(worktree_path), "checkout", "--force", "FETCH_HEAD"])
else:
print(f"Setting up worktree for ref '{ref}' at {worktree_path}...")
_run(
[
"git",
f"--git-dir={bare_repo_path}",
"worktree",
"add",
str(worktree_path),
ref,
]
)

otdfctl_dir = worktree_path / "otdfctl"
if not otdfctl_dir.is_dir():
raise RuntimeError(
f"Platform checkout at {worktree_path} has no otdfctl/ directory; "
f"ref '{ref}' predates the otdfctl monorepo migration (v0.31.0)."
)

src_link.parent.mkdir(parents=True, exist_ok=True)
if src_link.is_symlink():
src_link.unlink()
elif src_link.exists():
if src_link.is_dir():
shutil.rmtree(src_link)
else:
src_link.unlink()
src_link.symlink_to(Path("..") / "platform-src" / local_name / "otdfctl")
print(f"Symlinked {src_link} → {otdfctl_dir}")

return worktree_path
6 changes: 1 addition & 5 deletions otdf-sdk-mgr/src/otdf_sdk_mgr/cli_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,12 @@ 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, source=source)
cmd_install(sdk, version, dist_name=dist_name)
except InstallError as e:
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1)
13 changes: 1 addition & 12 deletions otdf-sdk-mgr/src/otdf_sdk_mgr/cli_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

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

import typer
Expand Down Expand Up @@ -113,20 +112,10 @@ 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, go_source=go_source)
v = resolve(sdk, version, infix)
if is_resolve_success(v):
env = lookup_additional_options(sdk, v["tag"])
if env:
Expand Down
Loading
Loading