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
36 changes: 32 additions & 4 deletions .github/workflows/publish-foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,43 @@ jobs:
if: steps.meta.outputs.publish == 'true'
env:
FOUNDRY_ARTIFACT_REPOSITORY_RID: ${{ secrets.FOUNDRY_ARTIFACT_REPOSITORY_RID }}
FOUNDRY_TOKEN: ${{ secrets.FOUNDRY_TOKEN }}
FOUNDRY_REGISTRY_HOST: ${{ secrets.FOUNDRY_REGISTRY_HOST }}
# Preferred: OAuth2 Client Credentials (short-lived access token minted at runtime)
FOUNDRY_URL: ${{ secrets.FOUNDRY_URL }}
FOUNDRY_OAUTH_CLIENT_ID: ${{ secrets.FOUNDRY_OAUTH_CLIENT_ID }}
FOUNDRY_OAUTH_CLIENT_SECRET: ${{ secrets.FOUNDRY_OAUTH_CLIENT_SECRET }}
# Optional: explicitly request a scope in the token request.
# Some enrollments/app-scope configurations may require this.
FOUNDRY_OAUTH_SCOPE: ${{ secrets.FOUNDRY_OAUTH_SCOPE }}
# Fallback: legacy static token (e.g., user-generated / UI token)
FOUNDRY_TOKEN: ${{ secrets.FOUNDRY_TOKEN }}
run: |
set -euo pipefail
: "${FOUNDRY_ARTIFACT_REPOSITORY_RID:?missing FOUNDRY_ARTIFACT_REPOSITORY_RID secret}"
: "${FOUNDRY_TOKEN:?missing FOUNDRY_TOKEN secret}"
: "${FOUNDRY_REGISTRY_HOST:?missing FOUNDRY_REGISTRY_HOST secret}"
echo "${FOUNDRY_TOKEN}" | docker login -u "${FOUNDRY_ARTIFACT_REPOSITORY_RID}" --password-stdin "${FOUNDRY_REGISTRY_HOST}"

if [[ -n "${FOUNDRY_URL:-}" && -n "${FOUNDRY_OAUTH_CLIENT_ID:-}" && -n "${FOUNDRY_OAUTH_CLIENT_SECRET:-}" ]]; then
foundry_url="${FOUNDRY_URL%/}"
scope_arg=()
if [[ -n "${FOUNDRY_OAUTH_SCOPE:-}" ]]; then
scope_arg=(--data-urlencode "scope=${FOUNDRY_OAUTH_SCOPE}")
fi
token_json="$(
curl -fsS -X POST "${foundry_url}/multipass/api/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${FOUNDRY_OAUTH_CLIENT_ID}" \
--data-urlencode "client_secret=${FOUNDRY_OAUTH_CLIENT_SECRET}" \
"${scope_arg[@]}"
)"

oauth_token="$(python -c 'import json,sys; print(json.loads(sys.stdin.read())["access_token"])' <<<"${token_json}")"
echo "::add-mask::${oauth_token}"
echo "${oauth_token}" | docker login -u "${FOUNDRY_ARTIFACT_REPOSITORY_RID}" --password-stdin "${FOUNDRY_REGISTRY_HOST}"
else
: "${FOUNDRY_TOKEN:?missing FOUNDRY_TOKEN secret (or set FOUNDRY_URL + FOUNDRY_OAUTH_CLIENT_ID + FOUNDRY_OAUTH_CLIENT_SECRET)}"
echo "${FOUNDRY_TOKEN}" | docker login -u "${FOUNDRY_ARTIFACT_REPOSITORY_RID}" --password-stdin "${FOUNDRY_REGISTRY_HOST}"
fi

- name: Build (and maybe push)
env:
Expand Down Expand Up @@ -209,4 +238,3 @@ jobs:
echo "- Publish: no (build + validate only)"
fi
} >> "${GITHUB_STEP_SUMMARY}"

8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ commands.

## 5. Foundry OpenAPI Compute Module Deployment

This repo is configured to support a Foundry-friendly workflow: ship a Docker image that embeds an OpenAPI contract (as the `server.openapi` image label), then import FastAPI routes as Foundry functions via **Detect from OpenAPI specification**.

![Foundry function call from imported OpenAPI](assets/palantir-compute-modules-function-call.png)

More context + screenshots:

- `docs/foundry-auto-deploy.md`

Generate and validate the Foundry-constrained OpenAPI artifact:

```bash
Expand Down
Binary file added assets/palantir-artifact-repository.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/deploy-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ Mapping:
- Image label `server.openapi` is required and must exactly match `openapi.foundry.json`.
- Foundry registry should primarily use version tags (e.g. `0.1.2`) rather than `main-...` tags.

For a short explanation of the "OpenAPI -> functions" import path (plus screenshots), see `docs/foundry-auto-deploy.md`.

## Railway Runtime Notes

- Runtime continues to honor `PORT` with default `8080`.
Expand Down
70 changes: 70 additions & 0 deletions docs/foundry-auto-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Foundry Auto-Deploy (OpenAPI -> Functions)

This repo is set up so a tagged release can publish a Docker image to Foundry and make its FastAPI routes importable as Foundry **functions**.

The key idea: publish a container image that includes a Foundry-compatible OpenAPI contract (as the `server.openapi` image label). In Foundry, you can then run **Detect from OpenAPI specification** to auto-register the functions.

## What You Get

- A deterministic OpenAPI surface area (`openapi.foundry.json`) for Foundry import.
- A container image with the OpenAPI contract embedded in metadata.
- UI-driven function registration from the contract (no handwritten wrappers).

## How It Works (High Level)

1. Generate a Foundry-constrained OpenAPI spec.
2. Build a linux/amd64 image that embeds the spec as `server.openapi`.
3. Push the image to a Foundry Artifact Repository.
4. In Foundry Compute Modules, link the image tag and run **Detect from OpenAPI specification**.

## Screenshots

Compute module function-call flow (functions module + query panel):

![Foundry function call from imported OpenAPI](../assets/palantir-compute-modules-function-call.png)

OpenAPI schema view inside Foundry (this is the contract Foundry imports):

![Foundry OpenAPI schema view](../assets/palantir-compute-module-openapi-interop.png)

Artifact repository tags (images pushed by CI/CD show up here):

![Foundry artifact repository tags](../assets/palantir-artifact-repository.png)

Add any additional Foundry UI screenshots to `assets/` and reference them from this doc and `README.md`.

## Commands (Local)

Generate and validate the Foundry-constrained OpenAPI artifact:

```bash
uv run python scripts/deploy/foundry_openapi.py --generate --spec-path openapi.foundry.json
uv run python scripts/deploy/foundry_openapi.py --spec-path openapi.foundry.json
```

Build an image that includes the OpenAPI as metadata:

```bash
export OPENAPI_JSON="$(uv run python -c 'import json; print(json.dumps(json.load(open("openapi.foundry.json", encoding="utf-8")), separators=(",", ":")))')"

docker buildx build \
--platform linux/amd64 \
--build-arg SERVER_OPENAPI="${OPENAPI_JSON}" \
--tag "<registry>/<repo>/<image>:<tag>" \
--load \
.
```

## CI/CD (Recommended)

- `/.github/workflows/publish-foundry.yml` publishes to Foundry on tag builds (`v*`).
- `/.github/workflows/release-version.yml` can auto-create a `vX.Y.Z` tag after CI passes on `main`.

Foundry workflow docs live in:

- `docs/deploy-ci.md`
- `docs/foundry-openapi-runbook.md`

## Auth Notes

For CI, prefer a dedicated non-admin Foundry user that has **Edit** permission on the target Artifact Repository. Generate a long-lived token as that user and store it as the GitHub secret `FOUNDRY_TOKEN`.
Comment on lines +68 to +70
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Auth notes are inconsistent with other docs and missing OAuth2 option.

Two issues:

  1. Inconsistency: This doc recommends a "long-lived token" for FOUNDRY_TOKEN, but docs/deploy-ci.md (line 62) states FOUNDRY_TOKEN is "short-lived" and needs refreshing before each publish window.

  2. Missing OAuth2 guidance: The workflow now supports OAuth2 client credentials as the preferred authentication method (via FOUNDRY_URL, FOUNDRY_OAUTH_CLIENT_ID, FOUNDRY_OAUTH_CLIENT_SECRET), but this section only mentions the legacy FOUNDRY_TOKEN approach.

📝 Suggested update to align auth guidance
 ## Auth Notes

-For CI, prefer a dedicated non-admin Foundry user that has **Edit** permission on the target Artifact Repository. Generate a long-lived token as that user and store it as the GitHub secret `FOUNDRY_TOKEN`.
+For CI authentication, two options are available:
+
+1. **OAuth2 (preferred)**: Configure `FOUNDRY_URL`, `FOUNDRY_OAUTH_CLIENT_ID`, and `FOUNDRY_OAUTH_CLIENT_SECRET` secrets. The workflow mints a short-lived access token at runtime.
+
+2. **Legacy token**: Generate a token from a dedicated non-admin Foundry user with **Edit** permission on the target Artifact Repository. Store it as `FOUNDRY_TOKEN`. Note: This token is short-lived and must be refreshed before each publish window.
🤖 Prompt for AI Agents
In `@docs/foundry-auto-deploy.md` around lines 68 - 70, Update the "Auth Notes"
section to be consistent with docs/deploy-ci.md by changing the guidance about
FOUNDRY_TOKEN to indicate it is a short-lived token that requires refreshing per
publish window (instead of recommending a long‑lived token), and add a preferred
OAuth2 client credentials option describing the required vars FOUNDRY_URL,
FOUNDRY_OAUTH_CLIENT_ID, and FOUNDRY_OAUTH_CLIENT_SECRET as the recommended CI
auth method; keep the legacy FOUNDRY_TOKEN paragraph as an alternative and
advise creating a dedicated non‑admin Foundry user with Edit permission if using
it.

2 changes: 2 additions & 0 deletions docs/foundry-openapi-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This runbook captures the full M1-M7 flow for the FastAPI compute module import path.

For the conceptual overview and screenshots of the import UX, see `docs/foundry-auto-deploy.md`.

## Locked Targets

- `FOUNDRY_URL=https://23dimethyl.usw-3.palantirfoundry.com`
Expand Down