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
20 changes: 10 additions & 10 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:24-bookworm

FROM --platform=linux/amd64 quay.io/openshift/origin-cli:5.1 AS openshift_cli

FROM mcr.microsoft.com/devcontainers/javascript-node:24-bookworm

ARG OC_VERSION=4.22.11
ARG TARGETARCH
ARG KUBECTL_VERSION=v1.33.3
ARG HELM_VERSION=v3.18.4

COPY scripts/install-kubernetes-tools.sh /tmp/install-kubernetes-tools.sh
COPY --from=openshift_cli /usr/bin/oc /usr/local/bin/oc.amd64

RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates curl tar gzip; \
chmod +x /usr/local/bin/oc.amd64; \
printf '%s\n' '#!/bin/sh' \
'if [ "$(uname -m)" = "x86_64" ]; then' \
' exec /usr/local/bin/oc.amd64 "$@"' \
'fi' \
'echo "OpenShift oc 5.1 is only available for amd64 in this image build." >&2' \
'exit 1' > /usr/local/bin/oc; \
case "$TARGETARCH" in \
amd64) oc_archive="openshift-client-linux-${OC_VERSION}.tar.gz" ;; \
arm64) oc_archive="openshift-client-linux-arm64-${OC_VERSION}.tar.gz" ;; \
*) echo "Unsupported OpenShift CLI architecture: $TARGETARCH" >&2; exit 1 ;; \
esac; \
curl -fsSL -o /tmp/openshift-client.tar.gz "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${OC_VERSION}/${oc_archive}"; \
tar -xzf /tmp/openshift-client.tar.gz -C /usr/local/bin oc; \
chmod +x /usr/local/bin/oc; \
rm -f /tmp/openshift-client.tar.gz; \
rm -rf /var/lib/apt/lists/*; \
chmod +x /tmp/install-kubernetes-tools.sh; \
KUBECTL_VERSION="$KUBECTL_VERSION" HELM_VERSION="$HELM_VERSION" /tmp/install-kubernetes-tools.sh; \
Expand Down
1 change: 0 additions & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ services:
required: false
environment:
KUBECONFIG: ${KUBECONFIG:-/home/node/.kube/k3s.yaml}
KUBERNETES_API_URL: ${KUBERNETES_API_URL:-https://k3s-single-node:6443}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
Expand Down
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Framework ENV Vars
# DOCKER_API_URL=tcp://host.docker.internal:2375 # Set to use the Docker API (instead of docker.sock) when the framework makes docker calls.
# KUBERNETES_API_URL=https://k3s-single-node:6443 # Optional API endpoint for cluster-aware framework features.
# KUBECONFIG=/app/.kube/k3s.yaml # Kubeconfig mounted from the peer K3s service.
# For OpenShift, mount ./openshift/kubeconfig.yaml and set KUBECONFIG=/app/.kube-openshift/kubeconfig.yaml.
# For OpenShift, set KUBERNETES_API_URL to your cluster API endpoint.
DEPLOYMENT_IDENTIFIER_RESPONSE_FIELD=petname
# DEPLOYMENT_IDENTIFIER_API_URL=http://host.docker.internal:5123/petname # API endpoint for retrieving deployment identifier
# LABINFO_API_URL=http://host.docker.internal:5123/metadata # API endpoint for retrieving lab information
# UDF_DEPLOYMENT_API_URL=http://metadata.udf/deployment # API endpoint for UDF deployment metadata
# Document source switch: REMOTE_DOCS_REPO_SERVER decides where MD(X) documents are loaded from.
# Set (below) = documents are fetched from the remote repo defined by the other REMOTE_DOCS_* values.
# Commented out = documents are read from the local src/app/docs folder, and all REMOTE_DOCS_* values are ignored.
REMOTE_DOCS_REPO_SERVER="https://raw.githubusercontent.com"
REMOTE_DOCS_REPO_API_SERVER="https://api.github.com"
REMOTE_DOCS_REPO_OWNER=f5devcentral
Expand Down
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ updates:
directory: "/"
schedule:
interval: weekly
ignore:
- dependency-name: "eslint"
update-types: ["version-update:semver-major"]
- dependency-name: "@eslint/js"
update-types: ["version-update:semver-major"]
22 changes: 11 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,30 @@ RUN \
else echo "Lockfile not found." && exit 1; \
fi

FROM --platform=linux/amd64 quay.io/openshift/origin-cli:5.1 AS openshift_cli

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ARG OC_VERSION=4.22.11
ARG TARGETARCH
ARG KUBECTL_VERSION=v1.33.3
ARG HELM_VERSION=v3.18.4

COPY scripts/install-kubernetes-tools.sh /tmp/install-kubernetes-tools.sh
COPY --from=openshift_cli /usr/bin/oc /usr/local/bin/oc.amd64

# Add Docker CLI, kubectl, and Helm for container and Kubernetes workflows.
# Add Docker CLI, OpenShift CLI, kubectl, and Helm for container and Kubernetes workflows.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends docker.io curl ca-certificates tar gzip; \
chmod +x /usr/local/bin/oc.amd64; \
printf '%s\n' '#!/bin/sh' \
'if [ "$(uname -m)" = "x86_64" ]; then' \
' exec /usr/local/bin/oc.amd64 "$@"' \
'fi' \
'echo "OpenShift oc 5.1 is only available for amd64 in this image build." >&2' \
'exit 1' > /usr/local/bin/oc; \
case "$TARGETARCH" in \
amd64) oc_archive="openshift-client-linux-${OC_VERSION}.tar.gz" ;; \
arm64) oc_archive="openshift-client-linux-arm64-${OC_VERSION}.tar.gz" ;; \
*) echo "Unsupported OpenShift CLI architecture: $TARGETARCH" >&2; exit 1 ;; \
esac; \
curl -fsSL -o /tmp/openshift-client.tar.gz "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${OC_VERSION}/${oc_archive}"; \
tar -xzf /tmp/openshift-client.tar.gz -C /usr/local/bin oc; \
chmod +x /usr/local/bin/oc; \
rm -f /tmp/openshift-client.tar.gz; \
rm -rf /var/lib/apt/lists/*; \
chmod +x /tmp/install-kubernetes-tools.sh; \
KUBECTL_VERSION="$KUBECTL_VERSION" HELM_VERSION="$HELM_VERSION" /tmp/install-kubernetes-tools.sh; \
Expand Down
74 changes: 62 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,34 @@ npm run dev

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the lab markdown page by modifying `app/docs/nginx-one.mdx`. The page auto-updates as you edit the file.
You can start editing the lab markdown page by modifying the MD(X) files in `src/app/docs`. The page auto-updates as you edit the file. See [Document Source](#document-source) for how to switch between local and remote documents.

An optional Kubernetes peer cluster profile is available in the devcontainer Compose stack:
An optional Kubernetes peer cluster is available in the devcontainer Compose stack, gated behind the `k3s` Compose profile. It does not start by default, and Kubernetes commands report that the cluster is unavailable until it is enabled.

To enable it for every rebuild, create a `.devcontainer/.env` file containing:

```shell
# Rebuild devcontainer with default services (no optional k3s profile)
Dev Containers: Rebuild Container
COMPOSE_PROFILES=k3s
```

Then run `Dev Containers: Rebuild Container` from the command palette. Compose reads that file from the devcontainer project directory, so the profile is applied however VS Code is launched. The file is ignored by git, so it stays a per-developer setting.

As an alternative, export the variable in the shell you launch VS Code from:

# Rebuild devcontainer with optional k3s peer cluster
COMPOSE_PROFILES=k3s Dev Containers: Rebuild Container
```shell
export COMPOSE_PROFILES=k3s
code .
```

To start the cluster immediately without rebuilding the devcontainer:

```shell
docker compose -f .devcontainer/docker-compose.yml --profile k3s up -d k3s-single-node
```

For OpenShift access in devcontainer mode, provide `./openshift/kubeconfig.yaml` in the repository workspace and set:

1. `KUBECONFIG=/home/node/.kube-openshift/kubeconfig.yaml`
1. `KUBERNETES_API_URL=https://api.your-openshift.example:6443`

## "Production" Docker Deployment

Expand Down Expand Up @@ -130,9 +142,8 @@ OpenShift access is also available for connecting the framework to an external O
# Prepare an OpenShift kubeconfig at this path:
# ./openshift/kubeconfig.yaml

# Point the app at the OpenShift kubeconfig and API endpoint
# Point the app at the OpenShift kubeconfig
export KUBECONFIG=/app/.kube-openshift/kubeconfig.yaml
export KUBERNETES_API_URL=https://api.your-openshift.example:6443

# Start the framework stack
docker compose up -d
Expand Down Expand Up @@ -162,13 +173,52 @@ The framework and the K3s container should share a kubeconfig or equivalent acce

### OpenShift Option

The framework image includes the OpenShift `oc` client pinned to v5.1. Compose mounts `./openshift` into the framework container at `/app/.kube-openshift` (and `/home/node/.kube-openshift` in devcontainer mode), so OpenShift access uses your provided kubeconfig directly.
The framework image includes the OpenShift `oc` client pinned to v4.22.11, built natively for both `amd64` and `arm64`. Compose mounts `./openshift` into the framework container at `/app/.kube-openshift` (and `/home/node/.kube-openshift` in devcontainer mode), so OpenShift access uses your provided kubeconfig directly.

Use the following environment variables to point the framework at OpenShift:
Use the following environment variable to point the framework at OpenShift:

1. `KUBECONFIG=/app/.kube-openshift/kubeconfig.yaml`
1. `KUBERNETES_API_URL=https://api.your-openshift.example:6443`

### Environment

You will need to create your own `/.env` file to use remote MDX documents. Use the `/.env.example` as a template.

### Document Source

The framework loads lab documents from either the local file system or a remote GitHub repository. A single environment variable, `REMOTE_DOCS_REPO_SERVER`, controls which source is used.

| `REMOTE_DOCS_REPO_SERVER` | Document source |
|---------------------------|-------------------------------------------------------|
| Set | Remote repository defined by the other `REMOTE_DOCS_*` values |
| Unset or commented out | Local `src/app/docs` folder |

#### Local documents

Comment out `REMOTE_DOCS_REPO_SERVER` in your `.env` file:

```shell
# REMOTE_DOCS_REPO_SERVER="https://raw.githubusercontent.com"
```

Then place your `.md` or `.mdx` files in `src/app/docs`. The remaining `REMOTE_DOCS_*` values are ignored in this mode, so they can be left in place.

Each document needs frontmatter, because the document index is sorted by `order`:

```mdx
---
title: My Lab
description: What this page covers
order: 1
---
```

Notes for local mode:

1. Local files are read per request, so edits appear on refresh and `REMOTE_DOCS_REPO_CACHE_SECONDS` does not apply.
1. Relative image rewriting is only applied to remote documents. Reference images from the `public` folder instead, for example `/media/diagram.png`.

#### Remote documents

Set `REMOTE_DOCS_REPO_SERVER` along with the other `REMOTE_DOCS_*` values to fetch documents from a GitHub repository. Responses are cached for `REMOTE_DOCS_REPO_CACHE_SECONDS`, and `REMOTE_DOCS_REPO_MEDIA_PATH` is used to resolve relative image paths in the remote content.

Environment variables are read when the server starts, so restart the application after changing the document source.
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ services:
required: false
environment:
KUBECONFIG: ${KUBECONFIG:-/app/.kube/k3s.yaml}
KUBERNETES_API_URL: ${KUBERNETES_API_URL:-https://k3s-single-node:6443}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
Expand Down
11 changes: 11 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { createRequire } from "module";
import { defineConfig, globalIgnores } from "eslint/config";
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import nextTypeScript from "eslint-config-next/typescript";

// eslint-plugin-react's "detect" mode uses context.getFilename(), removed in ESLint 10.
const REACT_VERSION = createRequire(import.meta.url)("react/package.json").version;

export default defineConfig([
...nextCoreWebVitals,
...nextTypeScript,
{
settings: {
react: {
version: REACT_VERSION,
},
},
},
globalIgnores([
".next/**",
"out/**",
Expand Down
2 changes: 1 addition & 1 deletion jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jest.mock("next/image", () => ({
alt?: string;
[key: string]: unknown;
}) => {
const React = require("react");
const React = jest.requireActual<typeof import("react")>("react");
const { src, alt, ...rest } = props;

return React.createElement("img", {
Expand Down
Loading