From c28d7fecf23977a5f1a664aeab47d27c3915ac39 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Wed, 11 Mar 2026 22:03:24 +0100 Subject: [PATCH 1/2] docs: switch service installation guidance to operator flow Move getting-started service docs away from Helm toward operator-based installation, including OpenShift/OKD and Kubernetes+OLM paths, cert-manager/authentication examples, and API field references. Add a release workflow to publish the operator installer manifest as a GitHub release asset. Made-with: Cursor --- .../workflows/release-operator-installer.yaml | 38 ++ .../configuration/authentication.md | 197 ++++---- .../installation/service/index.md | 3 + .../installation/service/service-local.md | 56 +-- .../installation/service/service-operator.md | 451 ++++++++++++++++++ .../service/service-production.md | 239 +--------- python/uv.lock | 45 +- 7 files changed, 621 insertions(+), 408 deletions(-) create mode 100644 .github/workflows/release-operator-installer.yaml create mode 100644 python/docs/source/getting-started/installation/service/service-operator.md diff --git a/.github/workflows/release-operator-installer.yaml b/.github/workflows/release-operator-installer.yaml new file mode 100644 index 000000000..970b40fb3 --- /dev/null +++ b/.github/workflows/release-operator-installer.yaml @@ -0,0 +1,38 @@ +name: Release operator installer + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + publish-operator-installer: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: controller/deploy/operator/go.mod + + - name: Build operator installer manifest + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="${RELEASE_TAG#v}" + IMG="quay.io/jumpstarter-dev/jumpstarter-operator:${VERSION}" + make -C controller/deploy/operator build-installer IMG="${IMG}" + cp controller/deploy/operator/dist/install.yaml operator-installer.yaml + + - name: Upload installer to release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + gh release upload "${RELEASE_TAG}" operator-installer.yaml --clobber diff --git a/python/docs/source/getting-started/configuration/authentication.md b/python/docs/source/getting-started/configuration/authentication.md index ec83dea49..b6131ba51 100644 --- a/python/docs/source/getting-started/configuration/authentication.md +++ b/python/docs/source/getting-started/configuration/authentication.md @@ -4,10 +4,15 @@ Jumpstarter uses internally issued JWT tokens to authenticate clients and exporters by default. You can also configure Jumpstarter to use external OpenID Connect (OIDC) providers. +When installing with the operator, authentication is configured directly on the +`Jumpstarter` custom resource, under `spec.authentication`. + +For operator installation context, see +[Install with Operator](../installation/service/service-operator.md). + To use OIDC with your Jumpstarter installation: -1. Set the helm value `jumpstarter-controller.authenticationConfiguration` to a - valid `AuthenticationConfiguration` yaml configuration +1. Set `spec.authentication.jwt` on your `Jumpstarter` resource 2. Configure your OIDC provider to work with Jumpstarter 3. Create users with appropriate OIDC usernames @@ -22,22 +27,21 @@ Set up Keycloak for Jumpstarter authentication: - `Valid redirect URIs`: `http://localhost/callback` - Leave remaining fields as default -2. Use this configuration for - `jumpstarter-controller.authenticationConfiguration` during installation: +2. Configure `spec.authentication.jwt` on your `Jumpstarter` resource: ```yaml -apiVersion: jumpstarter.dev/v1alpha1 -kind: AuthenticationConfiguration -jwt: -- issuer: - url: https:///realms/ - certificateAuthority: - audiences: - - jumpstarter-cli - claimMappings: - username: - claim: preferred_username - prefix: "keycloak:" +spec: + authentication: + jwt: + - issuer: + url: https:///realms/ + certificateAuthority: + audiences: + - jumpstarter-cli + claimMappings: + username: + claim: preferred_username + prefix: "keycloak:" ``` Note, the HTTPS URL is mandatory, and you only need to include @@ -107,7 +111,7 @@ $ kubectl -n dex create secret tls dex-tls \ --key=pki/private/dex.dex.svc.cluster.local.key ``` -2. Install Dex with Helm using the following `values.yaml`: +2. Install Dex using your preferred deployment method with the following equivalent configuration: ```yaml https: @@ -161,31 +165,25 @@ $ kubectl create clusterrolebinding oidc-reviewer \ --group=system:unauthenticated ``` -Then install Dex: - -```console -$ helm repo add dex https://charts.dexidp.io -$ helm install --namespace dex --wait -f values.yaml dex dex/dex -``` +Then deploy Dex in the `dex` namespace with this configuration applied. -3. Configure Jumpstarter to trust Dex. Use this configuration for - `jumpstarter-controller.authenticationConfiguration` during installation: +3. Configure Jumpstarter to trust Dex by setting `spec.authentication.jwt`: ```yaml -apiVersion: jumpstarter.dev/v1alpha1 -kind: AuthenticationConfiguration -jwt: - - issuer: - url: https://dex.dex.svc.cluster.local:5556 - audiences: - - jumpstarter-cli - audienceMatchPolicy: MatchAny - certificateAuthority: | - - claimMappings: - username: - claim: "name" - prefix: "dex:" +spec: + authentication: + jwt: + - issuer: + url: https://dex.dex.svc.cluster.local:5556 + audiences: + - jumpstarter-cli + audienceMatchPolicy: MatchAny + certificateAuthority: | + + claimMappings: + username: + claim: "name" + prefix: "dex:" ``` 4. Create clients and exporters with appropriate OIDC usernames. Prefix the full @@ -225,9 +223,8 @@ $ jmp login --exporter \ ## Reference -The reference section provides a complete example of an -`AuthenticationConfiguration` resource with detailed comments. Use this as a -template for creating your own configuration. +The reference section provides a complete example of `spec.authentication.jwt` +with detailed comments. Use this as a template for your `Jumpstarter` resource. Key components include: @@ -237,62 +234,62 @@ Key components include: - User validation rules ```yaml -apiVersion: jumpstarter.dev/v1alpha1 -kind: AuthenticationConfiguration -# JWT authenticators for OIDC-issued tokens -jwt: -- issuer: - # URL of the OIDC provider (must use https://) - url: https://example.com - # Optional: override URL for discovery information - discoveryURL: https://discovery.example.com/.well-known/openid-configuration - # Optional: PEM encoded CA certificates for validation - certificateAuthority: - # List of acceptable token audiences - audiences: - - my-app - - my-other-app - # Required when multiple audiences are specified - audienceMatchPolicy: MatchAny - # rules applied to validate token claims to authenticate users. - claimValidationRules: - # Validate specific claim values - - claim: hd - requiredValue: example.com - # Alternative: use CEL expressions for complex validation - - expression: 'claims.hd == "example.com"' - message: the hd claim must be set to example.com - - expression: 'claims.exp - claims.nbf <= 86400' - message: total token lifetime must not exceed 24 hours - # Map OIDC claims to Jumpstarter user properties - claimMappings: - # Required: configure username mapping - username: - # JWT claim to use as username - claim: "sub" - # Prefix for username (required when claim is set) - prefix: "" - # Alternative: use CEL expression (mutually exclusive with claim+prefix) - # expression: 'claims.username + ":external-user"' - # Optional: configure groups mapping - groups: - claim: "sub" - prefix: "" - # Alternative: use CEL expression - # expression: 'claims.roles.split(",")' - # Optional: configure UID mapping - uid: - claim: 'sub' - # Alternative: use CEL expression - # expression: 'claims.sub' - # Optional: add extra attributes to UserInfo - extra: - - key: 'example.com/tenant' - valueExpression: 'claims.tenant' - # validation rules applied to the final user object. - userValidationRules: - - expression: "!user.username.startsWith('system:')" - message: 'username cannot used reserved system: prefix' - - expression: "user.groups.all(group, !group.startsWith('system:'))" - message: 'groups cannot used reserved system: prefix' +spec: + authentication: + # JWT authenticators for OIDC-issued tokens + jwt: + - issuer: + # URL of the OIDC provider (must use https://) + url: https://example.com + # Optional: override URL for discovery information + discoveryURL: https://discovery.example.com/.well-known/openid-configuration + # Optional: PEM encoded CA certificates for validation + certificateAuthority: + # List of acceptable token audiences + audiences: + - my-app + - my-other-app + # Required when multiple audiences are specified + audienceMatchPolicy: MatchAny + # rules applied to validate token claims to authenticate users. + claimValidationRules: + # Validate specific claim values + - claim: hd + requiredValue: example.com + # Alternative: use CEL expressions for complex validation + - expression: 'claims.hd == "example.com"' + message: the hd claim must be set to example.com + - expression: 'claims.exp - claims.nbf <= 86400' + message: total token lifetime must not exceed 24 hours + # Map OIDC claims to Jumpstarter user properties + claimMappings: + # Required: configure username mapping + username: + # JWT claim to use as username + claim: "sub" + # Prefix for username (required when claim is set) + prefix: "" + # Alternative: use CEL expression (mutually exclusive with claim+prefix) + # expression: 'claims.username + ":external-user"' + # Optional: configure groups mapping + groups: + claim: "sub" + prefix: "" + # Alternative: use CEL expression + # expression: 'claims.roles.split(",")' + # Optional: configure UID mapping + uid: + claim: 'sub' + # Alternative: use CEL expression + # expression: 'claims.sub' + # Optional: add extra attributes to UserInfo + extra: + - key: 'example.com/tenant' + valueExpression: 'claims.tenant' + # validation rules applied to the final user object. + userValidationRules: + - expression: "!user.username.startsWith('system:')" + message: 'username cannot used reserved system: prefix' + - expression: "user.groups.all(group, !group.startsWith('system:'))" + message: 'groups cannot used reserved system: prefix' ``` diff --git a/python/docs/source/getting-started/installation/service/index.md b/python/docs/source/getting-started/installation/service/index.md index 8e60d0147..f07dcc2d6 100644 --- a/python/docs/source/getting-started/installation/service/index.md +++ b/python/docs/source/getting-started/installation/service/index.md @@ -11,6 +11,7 @@ For most users, we recommend starting with a **local installation** to get famil service-local.md service-production.md +service-operator.md ``` ## Quick Start @@ -18,3 +19,5 @@ service-production.md **New to Jumpstarter?** Start with the [Local Installation](service-local.md) guide to get up and running quickly on your development machine. **Ready for production?** See the [Production Deployment](service-production.md) guide for Kubernetes and OpenShift clusters with proper security, monitoring, and ingress configurations. + +**Installing with the Kubernetes operator?** Use [Install with Operator](service-operator.md) for `Jumpstarter` CR examples using Kubernetes Ingress or OpenShift Routes. diff --git a/python/docs/source/getting-started/installation/service/service-local.md b/python/docs/source/getting-started/installation/service/service-local.md index 5fb4dd428..1da85e2bd 100644 --- a/python/docs/source/getting-started/installation/service/service-local.md +++ b/python/docs/source/getting-started/installation/service/service-local.md @@ -8,7 +8,6 @@ Before installing locally, ensure you have: - Docker or Podman installed (for kind) - `kubectl` installed and configured to access your cluster -- [Helm](https://helm.sh/docs/intro/install/) (version 3.x or newer) - Administrator access to your cluster (required for CRD installation) ## Install with Jumpstarter CLI @@ -56,7 +55,6 @@ Additional options for cluster creation: - Custom cluster name: Specify as the first argument (default: `jumpstarter-lab`) - `--kind `: Path to the kind binary to use for cluster management -- `--helm `: Path to the Helm binary to install the Jumpstarter service with - `--force-recreate`: Force recreate the cluster if it already exists (destroys all data) - `--kind-extra-args`: Pass additional arguments to kind cluster creation - `--skip-install`: Create the cluster without installing Jumpstarter @@ -72,7 +70,6 @@ Additional options for cluster creation: - Custom cluster name: Specify as the first argument (default: `jumpstarter-lab`) - `--minikube `: Path to the minikube binary to use for cluster management -- `--helm `: Path to the Helm binary to install the Jumpstarter service with - `--force-recreate`: Force recreate the cluster if it already exists (destroys all data) - `--minikube-extra-args`: Pass additional arguments to minikube cluster creation - `--skip-install`: Create the cluster without installing Jumpstarter @@ -211,61 +208,12 @@ $ minikube start --extra-config=apiserver.service-node-port-range=8000-9000 ``` ```` -### Install Local Jumpstarter with Helm +### Install Local Jumpstarter with Operator -For manual installation with Helm, use these commands: - -````{tab} kind -```{code-block} console -:substitutions: -$ export IP="X.X.X.X" # Enter the IP address of your computer on the local network -$ export BASEDOMAIN="jumpstarter.${IP}.nip.io" -$ export GRPC_ENDPOINT="grpc.${BASEDOMAIN}:8082" -$ export GRPC_ROUTER_ENDPOINT="router.${BASEDOMAIN}:8083" -$ helm upgrade jumpstarter --install oci://quay.io/jumpstarter-dev/helm/jumpstarter \ - --create-namespace --namespace jumpstarter-lab \ - --set global.baseDomain=${BASEDOMAIN} \ - --set jumpstarter-controller.grpc.endpoint=${GRPC_ENDPOINT} \ - --set jumpstarter-controller.grpc.routerEndpoint=${GRPC_ROUTER_ENDPOINT} \ - --set global.metrics.enabled=false \ - --set jumpstarter-controller.grpc.nodeport.enabled=true \ - --set jumpstarter-controller.grpc.mode=nodeport \ - --version={{controller_version}} -``` -```` - -````{tab} minikube -```{code-block} console -:substitutions: -$ export IP=$(minikube ip) -$ export BASEDOMAIN="jumpstarter.${IP}.nip.io" -$ export GRPC_ENDPOINT="grpc.${BASEDOMAIN}:8082" -$ export GRPC_ROUTER_ENDPOINT="router.${BASEDOMAIN}:8083" -$ helm upgrade jumpstarter --install oci://quay.io/jumpstarter-dev/helm/jumpstarter \ - --create-namespace --namespace jumpstarter-lab \ - --set global.baseDomain=${BASEDOMAIN} \ - --set jumpstarter-controller.grpc.endpoint=${GRPC_ENDPOINT} \ - --set jumpstarter-controller.grpc.routerEndpoint=${GRPC_ROUTER_ENDPOINT} \ - --set global.metrics.enabled=false \ - --set jumpstarter-controller.grpc.nodeport.enabled=true \ - --set jumpstarter-controller.grpc.nodeport.port=8082 \ - --set jumpstarter-controller.grpc.nodeport.routerPort=8083 \ - --set jumpstarter-controller.grpc.mode=nodeport \ - --version={{controller_version}} -``` -```` +For manual installation after creating the local cluster, follow [Install with Operator](service-operator.md). Use a `baseDomain` and endpoint addresses appropriate for your local environment (for example, `nip.io` based hostnames), then apply your `Jumpstarter` CR. To check the status of the installation, run: ```{code-block} console $ kubectl get pods -n jumpstarter-lab --watch -NAME READY STATUS RESTARTS AGE -jumpstarter-controller-cc74d879-6b22b 1/1 Running 0 48s -jumpstarter-secrets-w42z4 0/1 Completed 0 48s -``` - -To uninstall the Helm release, run: - -```{code-block} console -$ helm uninstall jumpstarter --namespace jumpstarter-lab ``` \ No newline at end of file diff --git a/python/docs/source/getting-started/installation/service/service-operator.md b/python/docs/source/getting-started/installation/service/service-operator.md new file mode 100644 index 000000000..2f2ab5a54 --- /dev/null +++ b/python/docs/source/getting-started/installation/service/service-operator.md @@ -0,0 +1,451 @@ +# Install with Operator + +This guide covers installing Jumpstarter with the Kubernetes operator, using: + +- **Ingress** on vanilla Kubernetes +- **Route** on OpenShift + +It mirrors how `make deploy METHOD=operator` deploys the operator and creates a `Jumpstarter` custom resource (CR), but uses production-friendly manifests and release artifacts. + +## Prerequisites + +- A Kubernetes, OpenShift, or OKD cluster +- `kubectl` (or `oc`) configured for your cluster +- Cluster-admin permissions (required to install CRDs and operator RBAC) +- A DNS domain for Jumpstarter service endpoints (for example, `jumpstarter.example.com`) +- An ingress controller on Kubernetes, or Routes on OpenShift/OKD + +```{note} +This page focuses on operator installation and core CR configuration. It does not cover full setup of external components such as Dex/other OIDC providers or cert-manager installation. +``` + +## Install the operator + +````{tab} Kubernetes (OLM installed) +If your Kubernetes cluster already has OLM, install the operator from OperatorHub and then continue with the `Jumpstarter` custom resource in this guide. + +OperatorHub package page: + +- [Jumpstarter Operator on OperatorHub](https://operatorhub.io/operator/jumpstarter-operator) + +```{note} +On vanilla Kubernetes, this OperatorHub path assumes OLM is already installed and configured in your cluster. +``` +```` + +````{tab} OpenShift / OKD (OperatorHub recommended) +1. Log in to the OpenShift/OKD web console with cluster-admin permissions. +2. Go to **Operators -> OperatorHub**. +3. Search for **Jumpstarter Operator** and install it. +4. Wait until the installed operator status is `Succeeded`. + +Verify from CLI: + +```{code-block} console +$ oc get csv -n openshift-operators | grep jumpstarter +``` +```` + +````{tab} OpenShift / OKD (CLI OLM subscription) +Create a `Subscription` (example: `subscription.yaml`): + +```yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: jumpstarter-operator + namespace: openshift-operators +spec: + channel: alpha + name: jumpstarter-operator + source: community-operators + sourceNamespace: openshift-marketplace + installPlanApproval: Automatic +``` + +Apply and verify: + +```{code-block} console +$ oc apply -f subscription.yaml +$ oc get csv -n openshift-operators | grep jumpstarter +``` +```` + +````{tab} Manual installer YAML (any cluster) +Apply the operator installer from a release asset: + +```{code-block} console +$ kubectl apply -f https://github.com/jumpstarter-dev/jumpstarter/releases/download//operator-installer.yaml +``` + +For example: + +```{code-block} console +$ kubectl apply -f https://github.com/jumpstarter-dev/jumpstarter/releases/download/v0.8.1/operator-installer.yaml +``` + +Wait for the operator deployment: + +```{code-block} console +$ kubectl wait --namespace jumpstarter-operator-system \ + --for=condition=available deployment/jumpstarter-operator-controller-manager \ + --timeout=120s +``` +```` + +## Create a namespace for Jumpstarter + +```{code-block} console +$ kubectl create namespace jumpstarter-lab +``` + +## Create a `Jumpstarter` custom resource + +The operator reconciles the `Jumpstarter` CR and creates Deployments, Services, and networking resources (Ingresses or Routes) for controller/router/login endpoints. + +````{tab} Kubernetes (Ingress) +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + controller: + image: quay.io/jumpstarter-dev/jumpstarter-controller:0.8.1-rc.2 + imagePullPolicy: IfNotPresent + replicas: 1 + grpc: + endpoints: + - address: grpc.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx + login: + endpoints: + - address: login.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx + routers: + image: quay.io/jumpstarter-dev/jumpstarter-controller:0.8.1-rc.2 + imagePullPolicy: IfNotPresent + replicas: 1 + grpc: + endpoints: + - address: router.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx +``` +```` + +````{tab} OpenShift / OKD (Route) +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + controller: + image: quay.io/jumpstarter-dev/jumpstarter-controller:0.8.1-rc.2 + imagePullPolicy: IfNotPresent + replicas: 1 + grpc: + endpoints: + - address: grpc.jumpstarter.example.com:443 + route: + enabled: true + login: + endpoints: + - address: login.jumpstarter.example.com:443 + route: + enabled: true + routers: + image: quay.io/jumpstarter-dev/jumpstarter-controller:0.8.1-rc.2 + imagePullPolicy: IfNotPresent + replicas: 1 + grpc: + endpoints: + - address: router.jumpstarter.example.com:443 + route: + enabled: true +``` +```` + +Save as `jumpstarter.yaml`, then apply: + +```{code-block} console +$ kubectl apply -f jumpstarter.yaml +``` + +## Verify deployment + +Check CR status and workloads: + +```{code-block} console +$ kubectl get jumpstarter -n jumpstarter-lab +$ kubectl get deploy,svc,ingress,route -n jumpstarter-lab +``` + +```{note} +`route` is only available on OpenShift/OKD. On vanilla Kubernetes, use `ingress`. +``` + +```{note} +For OpenShift/OKD, set `spec.baseDomain` to a domain that resolves to your route hosts (for example, `jumpstarter.example.com`). Ensure DNS is configured so these route hostnames resolve correctly. +``` + +## OAuth and cert-manager integration notes + +- **OAuth / OIDC integration**: Configure this through `spec.authentication.jwt` in the `Jumpstarter` CR (issuer URL, audiences, and claim mappings). The operator applies this configuration to controller runtime settings, but does not install or configure your identity provider. +- **cert-manager integration**: Set `spec.certManager.enabled: true` to let the operator manage server certificates. You can use operator-managed self-signed certificates or reference an existing `Issuer`/`ClusterIssuer` with `spec.certManager.server.issuerRef`. Installing and configuring cert-manager itself remains an external prerequisite. + +For detailed authentication examples and field-level reference, see [Authentication](../../configuration/authentication.md). + +## cert-manager configuration examples + +These examples are aligned with scenarios covered by the operator e2e tests in `controller/deploy/operator/test/e2e/e2e_test.go`. + +### Self-signed cert-manager mode + +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + server: + selfSigned: + enabled: true + controller: + grpc: + endpoints: + - address: grpc.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx + routers: + grpc: + endpoints: + - address: router.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx +``` + +The operator creates and uses: + +- `-selfsigned-issuer` +- `-ca` +- `-ca-issuer` +- `-controller-tls` +- `-router--tls` + +### External issuer reference (ClusterIssuer) + +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + server: + issuerRef: + name: my-cluster-issuer + kind: ClusterIssuer + controller: + grpc: + endpoints: + - address: grpc.jumpstarter.example.com:443 + route: + enabled: true + routers: + grpc: + endpoints: + - address: router.jumpstarter.example.com:443 + route: + enabled: true +``` + +In this mode, the operator issues certificates with your referenced issuer and does not create the self-signed issuer chain. + +### Login endpoint with cert-manager default TLS secret naming + +When cert-manager is enabled and `controller.login.tls.secretName` is not set, the generated login Ingress uses the default TLS secret name `login-tls`. + +For Ingress-based login endpoints, you can use `controller.login.endpoints[].ingress.annotations` to integrate with ACME issuers (for example Let's Encrypt) managed by cert-manager. + +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + server: + selfSigned: + enabled: true + controller: + login: + endpoints: + - address: login.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +``` + +### Login endpoint with explicit TLS secret + +If you want a specific login certificate secret, set `controller.login.tls.secretName`: + +```yaml +apiVersion: operator.jumpstarter.dev/v1alpha1 +kind: Jumpstarter +metadata: + name: jumpstarter + namespace: jumpstarter-lab +spec: + baseDomain: jumpstarter.example.com + certManager: + enabled: true + server: + selfSigned: + enabled: true + controller: + login: + tls: + secretName: login-custom-tls + endpoints: + - address: login.jumpstarter.example.com:443 + ingress: + enabled: true + class: nginx +``` + +## Operator behavior insights + +From the current operator implementation in `controller/deploy/operator`, these behaviors are useful to know when authoring manifests: + +- If `spec.baseDomain` is empty and the cluster exposes OpenShift Route APIs, the operator auto-detects the cluster domain and sets `spec.baseDomain` to `jumpstarter..`. +- If an endpoint has no enabled service type, the operator auto-selects one in this order: `route` (if available), then `ingress`, then `clusterIP`. +- gRPC endpoints (`controller.grpc`, `routers.grpc`) use TLS passthrough semantics in generated Ingress/Route resources; login endpoints use edge TLS termination. +- Controller and router auth secrets are created once with fixed names (`jumpstarter-controller-secret`, `jumpstarter-router-secret`) and are intentionally not owner-referenced, so they persist across CR deletion/recreation. +- Router replicas are implemented as one Deployment per replica, and `$(replica)` placeholders in endpoint addresses are substituted per replica. +- When router NodePort is enabled for multiple replicas, the operator offsets NodePort by replica index for router services. +- Even when cert-manager is disabled, the operator still creates `jumpstarter-service-ca-cert` (with empty `ca.crt`) for CLI discoverability. +- Status conditions are populated on the `Jumpstarter` resource and include deployment readiness plus cert-manager/certificate readiness when cert-manager is enabled. + +## Jumpstarter API field reference + +The `Jumpstarter` CRD is `operator.jumpstarter.dev/v1alpha1`. + +### Top-level spec fields + +| Field | Type | Description | +| --- | --- | --- | +| `spec.baseDomain` | `string` | Base DNS domain for generated endpoint hostnames (for example `grpc.`). | +| `spec.certManager` | `object` | Certificate management settings for controller/router/login TLS integration. | +| `spec.controller` | `object` | Controller deployment, endpoint, and runtime settings. | +| `spec.routers` | `object` | Router deployment scale, resources, topology, and endpoint settings. | +| `spec.authentication` | `object` | Internal, Kubernetes token, JWT, and auto-provisioning authentication settings. | + +### Controller and router fields + +| Field | Type | Description | +| --- | --- | --- | +| `spec.controller.image` | `string` | Controller container image. | +| `spec.controller.imagePullPolicy` | `string` | Pull policy (`Always`, `IfNotPresent`, `Never`). | +| `spec.controller.resources` | `object` | Controller resource requests/limits. | +| `spec.controller.replicas` | `integer` | Number of controller pods. | +| `spec.controller.exporterOptions.offlineTimeout` | `duration` | Timeout before exporter is considered offline. | +| `spec.controller.grpc.tls.certSecret` | `string` | Manual TLS secret name for controller gRPC when cert-manager is disabled. | +| `spec.controller.grpc.endpoints[]` | `array` | Controller gRPC endpoint definitions (address + exposure method). | +| `spec.controller.grpc.keepalive.*` | `object` | gRPC keepalive tuning options. | +| `spec.controller.login.tls.secretName` | `string` | Optional TLS secret used by login edge-termination ingress/route. | +| `spec.controller.login.endpoints[]` | `array` | Login endpoint definitions (address + exposure method). | +| `spec.routers.image` | `string` | Router container image. | +| `spec.routers.imagePullPolicy` | `string` | Pull policy (`Always`, `IfNotPresent`, `Never`). | +| `spec.routers.resources` | `object` | Router resource requests/limits. | +| `spec.routers.replicas` | `integer` | Router replica count (operator creates one deployment per replica). | +| `spec.routers.topologySpreadConstraints[]` | `array` | Pod spread constraints for router deployments. | +| `spec.routers.grpc.tls.certSecret` | `string` | Manual TLS secret name for router gRPC when cert-manager is disabled. | +| `spec.routers.grpc.endpoints[]` | `array` | Router endpoint definitions; supports `$(replica)` placeholder in address. | +| `spec.routers.grpc.keepalive.*` | `object` | Router gRPC keepalive tuning options. | + +### Authentication fields + +| Field | Type | Description | +| --- | --- | --- | +| `spec.authentication.internal.enabled` | `boolean` | Enables internal token-based auth. | +| `spec.authentication.internal.prefix` | `string` | Username/subject prefix for internal auth. | +| `spec.authentication.internal.tokenLifetime` | `duration` | Internal token validity period. | +| `spec.authentication.k8s.enabled` | `boolean` | Enables Kubernetes service account token auth. | +| `spec.authentication.jwt[]` | `array` | JWT authenticators (issuer, audiences, claim mappings). | +| `spec.authentication.autoProvisioning.enabled` | `boolean` | Auto-create users authenticated by external providers. | + +### cert-manager fields + +| Field | Type | Description | +| --- | --- | --- | +| `spec.certManager.enabled` | `boolean` | Enables operator cert-manager integration. | +| `spec.certManager.server.selfSigned.enabled` | `boolean` | Enables operator-managed self-signed CA mode. | +| `spec.certManager.server.selfSigned.caDuration` | `duration` | Self-signed CA certificate duration. | +| `spec.certManager.server.selfSigned.certDuration` | `duration` | Issued server certificate duration. | +| `spec.certManager.server.selfSigned.renewBefore` | `duration` | Renewal lead time before expiration. | +| `spec.certManager.server.issuerRef.name` | `string` | Existing Issuer/ClusterIssuer name. | +| `spec.certManager.server.issuerRef.kind` | `string` | `Issuer` or `ClusterIssuer`. | +| `spec.certManager.server.issuerRef.group` | `string` | Issuer API group (default `cert-manager.io`). | +| `spec.certManager.server.issuerRef.caBundle` | `bytes` | Optional PEM CA bundle published for clients. | + +### Endpoint schema (used in gRPC/login endpoint arrays) + +| Field | Type | Description | +| --- | --- | --- | +| `address` | `string` | Host/address, optional port, supports `$(replica)` for router endpoints. | +| `route.enabled` | `boolean` | Create OpenShift Route for endpoint. | +| `route.annotations` / `route.labels` | `map` | Route metadata overrides. | +| `ingress.enabled` | `boolean` | Create Kubernetes Ingress for endpoint. | +| `ingress.class` | `string` | Ingress class name. | +| `ingress.annotations` / `ingress.labels` | `map` | Ingress metadata overrides. | +| `nodeport.enabled` | `boolean` | Create NodePort service for endpoint. | +| `nodeport.port` | `integer` | Requested NodePort value. | +| `nodeport.annotations` / `nodeport.labels` | `map` | NodePort service metadata overrides. | +| `loadBalancer.enabled` | `boolean` | Create LoadBalancer service for endpoint. | +| `loadBalancer.port` | `integer` | Service port for LoadBalancer exposure. | +| `loadBalancer.annotations` / `loadBalancer.labels` | `map` | LoadBalancer service metadata overrides. | +| `clusterIP.enabled` | `boolean` | Create ClusterIP service for endpoint. | +| `clusterIP.annotations` / `clusterIP.labels` | `map` | ClusterIP service metadata overrides. | + +### Status conditions + +| Condition type | Meaning | +| --- | --- | +| `Ready` | Overall operator-managed deployment readiness. | +| `ControllerDeploymentReady` | Controller deployment is available. | +| `RouterDeploymentsReady` | All router deployments are available. | +| `CertManagerAvailable` | cert-manager CRDs are present (when enabled). | +| `IssuerReady` | Configured issuer is ready (when enabled). | +| `ControllerCertificateReady` | Controller TLS secret is ready (when enabled). | +| `RouterCertificatesReady` | Router TLS secrets are ready for all replicas (when enabled). | + diff --git a/python/docs/source/getting-started/installation/service/service-production.md b/python/docs/source/getting-started/installation/service/service-production.md index 0fb396a23..12790aee1 100644 --- a/python/docs/source/getting-started/installation/service/service-production.md +++ b/python/docs/source/getting-started/installation/service/service-production.md @@ -8,7 +8,6 @@ Before installing in production, ensure you have: - A production Kubernetes cluster available - `kubectl` installed and configured to access your cluster -- [Helm](https://helm.sh/docs/intro/install/) (version 3.x or newer) - Administrator access to your cluster (required for CRD installation) - Domain name for service endpoints - Ingress controller installed (for Kubernetes) or Routes configured (for OpenShift) @@ -47,237 +46,15 @@ Choose one of these TLS termination approaches: gRPC over HTTP/1.1 is not supported. Ensure your ingress controller supports HTTP/2 and is properly configured for gRPC traffic. ``` -## Install with Helm +## Install with Operator -Install Jumpstarter on a Kubernetes/OpenShift cluster using Helm: +If you prefer operator-managed installation and reconciliation, see [Install with Operator](service-operator.md). That guide includes: -````{tab} Kubernetes -```{code-block} console -:substitutions: -$ helm upgrade jumpstarter --install oci://quay.io/jumpstarter-dev/helm/jumpstarter \ - --create-namespace --namespace jumpstarter-lab \ - --set global.baseDomain=jumpstarter.example.com \ - --set global.metrics.enabled=true \ - --set jumpstarter-controller.grpc.mode=ingress \ - --version={{controller_version}} -``` -```` - -````{tab} OpenShift -```{code-block} console -:substitutions: -$ helm upgrade jumpstarter --install oci://quay.io/jumpstarter-dev/helm/jumpstarter \ - --create-namespace --namespace jumpstarter-lab \ - --set global.baseDomain=jumpstarter.example.com \ - --set global.metrics.enabled=true \ - --set jumpstarter-controller.grpc.mode=route \ - --version={{controller_version}} -``` - -**OpenShift Route TLS Configuration:** - -OpenShift automatically creates secure routes with TLS termination. For custom certificates: - -```yaml -apiVersion: route.openshift.io/v1 -kind: Route -metadata: - name: jumpstarter-grpc - annotations: - haproxy.router.openshift.io/balance: source - haproxy.router.openshift.io/timeout: 300s -spec: - host: grpc.jumpstarter.example.com - tls: - termination: edge - certificate: | - -----BEGIN CERTIFICATE----- - # Your certificate here - -----END CERTIFICATE----- - key: | - -----BEGIN PRIVATE KEY----- - # Your private key here - -----END PRIVATE KEY----- - to: - kind: Service - name: jumpstarter-controller-grpc - weight: 100 -``` -```` - -## Install with ArgoCD - -You can use ArgoCD to install Jumpstarter in your production cluster. Below are examples for different platforms: - -````{tab} Kubernetes -### Install with ArgoCD on Kubernetes (Amazon EKS) - -First, create a namespace for Jumpstarter: - -```console -$ kubectl create namespace jumpstarter-lab -``` - -If your ArgoCD installation requires namespace labeling for management, add the appropriate label: - -```console -$ kubectl label namespace jumpstarter-lab argocd.argoproj.io/managed-by=argocd -``` - -For ArgoCD to manage Jumpstarter CRDs, create this `ClusterRole` and `ClusterRoleBinding`: - -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: argocd-application-controller-crd -rules: -- apiGroups: - - 'apiextensions.k8s.io' - resources: - - 'customresourcedefinitions' - verbs: - - '*' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: argocd-application-controller-crd -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: argocd-application-controller-crd -subjects: -- kind: ServiceAccount - name: argocd-application-controller - namespace: argocd # Replace with your ArgoCD namespace -``` - -Create an ArgoCD Application to deploy Jumpstarter: - -```{warning} -The secrets `jumpstarter-controller.controllerSecret` and `jumpstarter-controller.routerSecret` -must be unique for each installation. While Helm can auto-generate these, ArgoCD cannot - -you must manually create these in your Jumpstarter namespace. -``` - -```{code-block} yaml -:substitutions: -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: jumpstarter - namespace: argocd # Replace with your ArgoCD namespace -spec: - destination: - name: in-cluster - namespace: jumpstarter-lab - project: default - source: - chart: jumpstarter - helm: - parameters: - - name: global.baseDomain - value: jumpstarter.example.com - - name: global.metrics.enabled - value: "true" - - name: jumpstarter-controller.controllerSecret - value: "pick-a-secret-DONT-USE-THIS-DEFAULT" - - name: jumpstarter-controller.routerSecret - value: "again-pick-a-secret-DONT-USE-THIS-DEFAULT" - - name: jumpstarter-controller.grpc.mode - value: "ingress" - repoURL: quay.io/jumpstarter-dev/helm - targetRevision: "{{controller_version}}" - syncPolicy: - automated: - prune: true - selfHeal: true - syncOptions: - - CreateNamespace=true -``` -```` - -````{tab} OpenShift -### Install with ArgoCD on OpenShift - -First, create and label a namespace for Jumpstarter: +- Installing the operator from the release asset (`operator-installer.yaml`) +- Creating a `Jumpstarter` custom resource for vanilla Kubernetes with Ingress +- Creating a `Jumpstarter` custom resource for OpenShift with Routes +- Notes on integrating external OAuth/OIDC and cert-manager setups -```console -$ kubectl create namespace jumpstarter-lab -$ kubectl label namespace jumpstarter-lab argocd.argoproj.io/managed-by=openshift-gitops -``` - -For ArgoCD to manage Jumpstarter CRDs, create this `ClusterRole` and `ClusterRoleBinding`: - -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - annotations: - argocds.argoproj.io/name: openshift-gitops - argocds.argoproj.io/namespace: openshift-gitops - name: openshift-gitops-argocd-appcontroller-crd -rules: -- apiGroups: - - 'apiextensions.k8s.io' - resources: - - 'customresourcedefinitions' - verbs: - - '*' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - annotations: - argocds.argoproj.io/name: openshift-gitops - argocds.argoproj.io/namespace: openshift-gitops - name: openshift-gitops-argocd-appcontroller-crd -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: openshift-gitops-argocd-appcontroller-crd -subjects: -- kind: ServiceAccount - name: openshift-gitops-argocd-application-controller - namespace: openshift-gitops -``` - -Create an ArgoCD Application to deploy Jumpstarter: +## GitOps and ArgoCD -```{warning} -The secrets `jumpstarter-controller.controllerSecret` and `jumpstarter-controller.routerSecret` -must be unique for each installation. While Helm can auto-generate these, ArgoCD cannot - -you must manually create these in your Jumpstarter namespace. -``` - -```{code-block} yaml -:substitutions: -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: jumpstarter - namespace: openshift-gitops -spec: - destination: - name: in-cluster - namespace: jumpstarter-lab - project: default - source: - chart: jumpstarter - helm: - parameters: - - name: global.baseDomain - value: jumpstarter.example.com - - name: global.metrics.enabled - value: "true" - - name: jumpstarter-controller.controllerSecret - value: "pick-a-secret-DONT-USE-THIS-DEFAULT" - - name: jumpstarter-controller.routerSecret - value: "again-pick-a-secret-DONT-USE-THIS-DEFAULT" - - name: jumpstarter-controller.grpc.mode - value: "route" - repoURL: quay.io/jumpstarter-dev/helm - targetRevision: "{{controller_version}}" -``` -```` \ No newline at end of file +Use the operator installer and manage your `Jumpstarter` custom resource declaratively in GitOps flows. See [Install with Operator](service-operator.md) for the manifests and endpoint patterns to use on Kubernetes (Ingress) and OpenShift (Route). \ No newline at end of file diff --git a/python/uv.lock b/python/uv.lock index 0e3b6d16e..19c557e4b 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -53,7 +53,7 @@ members = [ dev = [ { name = "esbonio", specifier = ">=0.16.5" }, { name = "pre-commit", specifier = ">=3.8.0" }, - { name = "ruff", specifier = "==0.14.14" }, + { name = "ruff", specifier = "==0.15.2" }, { name = "ty", specifier = ">=0.0.1a8" }, { name = "typos", specifier = ">=1.23.6" }, ] @@ -3893,28 +3893,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] [[package]] From 292923fe54907cd0c0e095689acc4eeadb7a1497 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Thu, 12 Mar 2026 09:34:25 +0100 Subject: [PATCH 2/2] docs: fix auth validation text and platform-specific verify commands Correct grammar in authentication validation rule messages and split service verification commands so Kubernetes and OpenShift/OKD use supported resource types. --- .../configuration/authentication.md | 16 +++++++++++----- .../installation/service/service-operator.md | 3 ++- .../installation/service/service-production.md | 6 +++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/python/docs/source/getting-started/configuration/authentication.md b/python/docs/source/getting-started/configuration/authentication.md index b6131ba51..f37886b46 100644 --- a/python/docs/source/getting-started/configuration/authentication.md +++ b/python/docs/source/getting-started/configuration/authentication.md @@ -111,7 +111,7 @@ $ kubectl -n dex create secret tls dex-tls \ --key=pki/private/dex.dex.svc.cluster.local.key ``` -2. Install Dex using your preferred deployment method with the following equivalent configuration: +2. Install Dex with Helm using the following `values.yaml`: ```yaml https: @@ -165,9 +165,15 @@ $ kubectl create clusterrolebinding oidc-reviewer \ --group=system:unauthenticated ``` -Then deploy Dex in the `dex` namespace with this configuration applied. +Then install Dex: -3. Configure Jumpstarter to trust Dex by setting `spec.authentication.jwt`: +```console +$ helm repo add dex https://charts.dexidp.io +$ helm install --namespace dex --wait -f values.yaml dex dex/dex +``` + +3. Configure Jumpstarter to trust Dex. Use this configuration for + `jumpstarter-controller.authenticationConfiguration` during installation: ```yaml spec: @@ -289,7 +295,7 @@ spec: # validation rules applied to the final user object. userValidationRules: - expression: "!user.username.startsWith('system:')" - message: 'username cannot used reserved system: prefix' + message: 'username cannot use reserved system: prefix' - expression: "user.groups.all(group, !group.startsWith('system:'))" - message: 'groups cannot used reserved system: prefix' + message: 'groups cannot use reserved system: prefix' ``` diff --git a/python/docs/source/getting-started/installation/service/service-operator.md b/python/docs/source/getting-started/installation/service/service-operator.md index 2f2ab5a54..f96d76544 100644 --- a/python/docs/source/getting-started/installation/service/service-operator.md +++ b/python/docs/source/getting-started/installation/service/service-operator.md @@ -192,7 +192,8 @@ Check CR status and workloads: ```{code-block} console $ kubectl get jumpstarter -n jumpstarter-lab -$ kubectl get deploy,svc,ingress,route -n jumpstarter-lab +$ kubectl get deploy,svc,ingress -n jumpstarter-lab # Kubernetes +$ kubectl get deploy,svc,route -n jumpstarter-lab # OpenShift/OKD ``` ```{note} diff --git a/python/docs/source/getting-started/installation/service/service-production.md b/python/docs/source/getting-started/installation/service/service-production.md index 12790aee1..6d3346025 100644 --- a/python/docs/source/getting-started/installation/service/service-production.md +++ b/python/docs/source/getting-started/installation/service/service-production.md @@ -46,11 +46,11 @@ Choose one of these TLS termination approaches: gRPC over HTTP/1.1 is not supported. Ensure your ingress controller supports HTTP/2 and is properly configured for gRPC traffic. ``` -## Install with Operator +## Installation -If you prefer operator-managed installation and reconciliation, see [Install with Operator](service-operator.md). That guide includes: +To install Jumpstarter, see [Install with Operator](service-operator.md). That guide includes: -- Installing the operator from the release asset (`operator-installer.yaml`) +- Installing the operator from the release asset (`operator-installer.yaml`), OperatorHub and OLM. - Creating a `Jumpstarter` custom resource for vanilla Kubernetes with Ingress - Creating a `Jumpstarter` custom resource for OpenShift with Routes - Notes on integrating external OAuth/OIDC and cert-manager setups