Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
113 changes: 40 additions & 73 deletions .github/workflows/xtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ on:
otdfctl-source:
required: false
type: string
default: auto
description: "otdfctl source: 'auto' (standalone for releases, detect platform for head builds), 'standalone', or 'platform'"
default: ""
description: "DEPRECATED — ignored. otdfctl is always sourced from the opentdf/platform monorepo; pre-v0.31.0 tags fall back to the Go module proxy for artifact install."
workflow_call:
inputs:
platform-ref:
Expand All @@ -56,9 +56,14 @@ on:
type: string
default: all
otdfctl-source:
# DEPRECATED — accepted but ignored. otdfctl is always sourced from
# the opentdf/platform monorepo; pre-v0.31.0 tags fall back to the
# Go module proxy for artifact install. Kept declared so existing
# workflow_call sites that pass this input don't fail with
# "Unexpected inputs provided".
required: false
type: string
default: auto
default: ""
schedule:
- cron: "30 6 * * *" # 0630 UTC
- cron: "0 5 * * 1,3" # 500 UTC (Monday, Wednesday)
Expand Down Expand Up @@ -88,14 +93,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 All @@ -106,6 +103,12 @@ jobs:
echo "Invalid focus-sdk input: ${FOCUS_SDK_INPUT}. Must be one of: all, go, java, js." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
- name: Warn on deprecated otdfctl-source input
if: ${{ inputs.otdfctl-source != '' }}
env:
OTDFCTL_SOURCE_INPUT: ${{ inputs.otdfctl-source }}
run: |-
echo "::warning::Input 'otdfctl-source=${OTDFCTL_SOURCE_INPUT}' is deprecated and ignored. otdfctl is now always sourced from the opentdf/platform monorepo; pre-v0.31.0 tags fall back to the Go module proxy. Remove this input from your caller."
- name: Default Versions depend on context
id: default-tags
run: |-
Expand Down Expand Up @@ -322,41 +325,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 +384,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 +398,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
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
57 changes: 18 additions & 39 deletions otdf-sdk-mgr/src/otdf_sdk_mgr/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ def get_sdk_dirs() -> dict[str, Path]:
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"
# Module paths keyed by source. "platform" is the canonical, post-migration
# location; "standalone" covers archived pre-v0.31.0 release artifacts that
# remain installable via the Go module proxy.
GO_MODULE_PATHS: dict[str, str] = {
"platform": "github.com/opentdf/platform/otdfctl",
"standalone": "github.com/opentdf/otdfctl",
}

LTS_VERSIONS: dict[str, str] = {
"go": "0.24.0",
Expand Down Expand Up @@ -102,9 +107,10 @@ def get_sdk_dirs() -> dict[str, Path]:
"0.1.0": "protocol/go/v0.2.3",
}

# Bare repo names per SDK (used by checkout)
# Bare repo names per SDK (used by checkout). "go" is intentionally absent —
# otdfctl source builds come from the opentdf/platform monorepo via
# checkout_go_from_platform, not from a standalone clone.
SDK_BARE_REPOS: dict[str, str] = {
"go": "otdfctl.git",
"java": "java-sdk.git",
"js": "web-sdk.git",
}
Expand All @@ -119,42 +125,15 @@ def get_sdk_dirs() -> dict[str, Path]:
# (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
def go_module_path(source: str) -> str:
"""Return the Go module path for the given source ("platform" or "standalone")."""
try:
return GO_MODULE_PATHS[source]
except KeyError as e:
raise ValueError(
f"Invalid Go source {source!r}; expected one of {sorted(GO_MODULE_PATHS)}"
) from e


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