applyapi

package
v0.7.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

pkg/gateway/applyapi

applyapi implements the Orkestra Apply API — a CRUD REST surface for Kubernetes Custom Resources served by the gateway process. It lets any HTTP client create, read, or delete CRs managed by Orkestra without a kubeconfig or kubectl.

POST   /api/v1/apply
GET    /api/v1/resources/{kind}/{namespace}/{name}
GET    /api/v1/resources/{kind}/{namespace}
DELETE /api/v1/resources/{kind}/{namespace}/{name}
GET    /api/v1/schema/          ← service catalog (all IDP-enabled CRDs)
GET    /api/v1/schema/{kind}    ← CRD schema + idpFields

Enabling

The Apply API is off by default. Enable it in the Katalog:

gateway:
  applyAPI:
    enabled: true
    auth:
      tokens:
        - name: ci-pipeline
          token: "${CI_ORK_TOKEN}"

Enable the Create button in the Control Center per CRD:

spec:
  crds:
    application:
      idp:
        enabled: true

What callers can do

Caller Path
Control Center POST + GET to render a form, submit, and display status
CI pipeline curl POST /api/v1/apply — no kubeconfig in the runner
Terraform provider create → POST, read → GET, destroy → DELETE
Slack bot parse command → build CR → POST → poll GET → reply
Any HTTP client any language, any platform

Security — admission rules, namespace protection, deletion protection — is enforced exactly as it is for kubectl apply through the gateway webhook path. No new configuration.

Developer documentation

I want to… Go to
Understand why the Apply API lives in the gateway docs/01-overview.md
Understand the full apply pipeline (auth → validation → admission → SSA) docs/02-apply.md
Understand the schema endpoint and how CRD schemas are resolved docs/03-schema.md
Understand GET and DELETE, deletion protection, polling patterns docs/04-resources.md
Understand token auth, add a new auth mode docs/05-auth.md

Documentation

Overview

pkg/gateway/apply/handler.go

POST /api/v1/apply

Pipeline: auth (handled by middleware) → decode body → SSA → translate response.

The Apply API does not duplicate admission logic. Webhooks enforce admission rules when enabled; the reconciler enforces them otherwise. This handler's only job is to accept a CR body, apply it via server-side apply, and return a structured result.

pkg/gateway/auth.go

Bearer token authentication for the Apply API.

At startup, LoadTokens resolves every token entry in the Katalog config:

  • secretRef entries: read or self-bootstrap the Kubernetes Secret, then optionally rotate it if rotateAfter has elapsed.
  • token entries: expand ${ENV_VAR} references.

The resolved values are stored in TokenSet, which the auth middleware uses to validate incoming Authorization: Bearer <token> headers.

Self-bootstrap and rotation reuse pkg/secrets helpers so the annotation-based rotation clock is identical to operator-managed secrets.

pkg/gateway/resources/handler.go

GET /api/v1/resources/{kind}/{namespace}/{name} — read one CR GET /api/v1/resources/{kind}/{namespace} — list CRs in a namespace DELETE /api/v1/resources/{kind}/{namespace}/{name} — delete a CR

These endpoints are for external callers (CI, Terraform, Slack bots) that need raw Kubernetes CR state without kubeconfig. The Control Center uses the runtime's /katalog/{crd}/cr/... endpoints (richer, informer-cached); these endpoints call the Kubernetes API directly via the dynamic client.

Deletion protection is enforced by the admission webhook when enabled. This handler does not duplicate that check — it issues the DELETE and lets Kubernetes reject it if the webhook blocks.

pkg/gateway/schema/handler.go

GET /api/v1/schema/{kind} — spec properties + idpFields for one CRD GET /api/v1/schema/ — catalog: list of all IDP-enabled CRDs

Only served for CRDs where idp.enabled: true. The Control Center uses these endpoints to render the [+ Create] form and the service catalog picker.

pkg/gateway/applyapi/server.go

ApplyAPIServer wires the Apply API handlers onto a health.HealthServer. Called from cmd/internal/gateway.go after the kubeclient is ready.

Route layout:

POST   /api/v1/apply                          → applyHandler
GET    /api/v1/resources/{kind}/{ns}[/{name}] → resourcesHandler
DELETE /api/v1/resources/{kind}/{ns}/{name}   → resourcesHandler
GET    /api/v1/schema/                        → schemaHandler (service catalog — IDP-enabled CRDs)
GET    /api/v1/schema/{kind}                  → schemaHandler (CRD schema + idpFields)

All routes are wrapped by AuthMiddleware before registration.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AuthMiddleware

func AuthMiddleware(ts *TokenSet, next http.Handler) http.Handler

AuthMiddleware returns an http.Handler that validates the Authorization: Bearer header against the TokenSet. On match, the token name is added to the request context. On mismatch, responds 401.

func TokenNameFromContext

func TokenNameFromContext(ctx context.Context) string

TokenNameFromContext returns the authenticated token name from the context. Empty string when the request was not authenticated (should not happen past middleware).

Types

type ApplyAPIServer

type ApplyAPIServer struct {
	// contains filtered or unexported fields
}

ApplyAPIServer holds the resolved token set and wired handlers.

func NewApplyAPIServer

func NewApplyAPIServer(ctx context.Context, kat *katalog.Katalog, kube kubeclient.KubeClient, ownNS string) (*ApplyAPIServer, error)

NewApplyAPIServer resolves all tokens (bootstrapping/rotating Secrets as needed) and returns a ready-to-register server.

func (*ApplyAPIServer) Register

func (s *ApplyAPIServer) Register(reg Registrar)

Register wires all Apply API routes onto the given Registrar.

func (*ApplyAPIServer) ReloadTokens

func (s *ApplyAPIServer) ReloadTokens(ctx context.Context) error

ReloadTokens re-resolves all secretRef tokens (recreating any missing secrets) and atomically replaces the in-memory TokenSet. Called by the housekeeper when a token secret is found to be missing during reconciliation.

type ApplyResponse

type ApplyResponse struct {
	// Accepted is true when the CR was applied (or would be applied, for dry runs) without error.
	Accepted bool `json:"accepted"`
	// DryRun is true when the request was a dry-run preview (?dryRun=true).
	DryRun bool `json:"dryRun,omitempty"`
	// Name is the CR name as stored in Kubernetes.
	Name string `json:"name,omitempty"`
	// Namespace is the CR namespace.
	Namespace string `json:"namespace,omitempty"`
	// Kind is the CR kind.
	Kind string `json:"kind,omitempty"`
	// APIVersion is the CR apiVersion.
	APIVersion string `json:"apiVersion,omitempty"`
	// ResourceVersion is the Kubernetes resourceVersion after apply.
	ResourceVersion string `json:"resourceVersion,omitempty"`
	// Message carries the rejection reason on Accepted=false.
	Message string `json:"message,omitempty"`
	// Warnings is a list of advisory messages returned by the admission webhook
	// even when the request was accepted. Mirrors the kubectl warning experience.
	Warnings []string `json:"warnings,omitempty"`
	// Violations is a structured list of field-level errors from admission or
	// validation. Populated when Accepted=false and kubernetes returns Status details.
	Violations []ApplyViolation `json:"violations,omitempty"`
}

ApplyResponse is returned for every POST /api/v1/apply request.

type ApplyViolation

type ApplyViolation struct {
	// Field is the dot-notation path to the offending field (e.g. "spec.environment").
	Field string `json:"field,omitempty"`
	// Message is the human-readable error from Kubernetes or the admission webhook.
	Message string `json:"message"`
	// Severity is "error" (blocks apply) or "warning" (informational).
	Severity string `json:"severity,omitempty"`
}

ApplyViolation is one field-level error returned from Kubernetes on a failed apply.

type CRDLookup

type CRDLookup func(kind string) *orktypes.CRDEntry

CRDLookup returns the CRDEntry for a given kind, or nil if not found or not IDP-enabled.

type CatalogEntry

type CatalogEntry struct {
	Kind        string `json:"kind"`
	APIVersion  string `json:"apiVersion"`
	Category    string `json:"category,omitempty"`
	Description string `json:"description,omitempty"`
}

CatalogEntry is one row in the service catalog.

type CatalogLister

type CatalogLister func() []*orktypes.CRDEntry

CatalogLister returns all IDP-enabled CRDEntries.

type CatalogResponse

type CatalogResponse struct {
	Schemas []CatalogEntry `json:"schemas"`
}

CatalogResponse is returned by GET /api/v1/schema/ (no kind).

type KindMapper

type KindMapper func(kind string) (schema.GroupVersionResource, error)

KindMapper resolves a kind name to a GroupVersionResource. Provided by the caller (gateway server) so this package stays pure HTTP.

type Registrar

type Registrar interface {
	Register(path string, handler http.HandlerFunc)
}

Registrar is the subset of health.HealthServer used here. Defined as an interface so the server package does not import pkg/health.

type SchemaResponse

type SchemaResponse struct {
	Kind         string                             `json:"kind"`
	APIVersion   string                             `json:"apiVersion"`
	Properties   map[string]interface{}             `json:"properties"`
	Required     []string                           `json:"required,omitempty"`
	IDPFields    map[string]orktypes.IDPFieldConfig `json:"idpFields,omitempty"`
	IgnoreFields []string                           `json:"ignoreFields,omitempty"`
}

SchemaResponse is returned by GET /api/v1/schema/{kind}.

type TokenSet

type TokenSet struct {
	// contains filtered or unexported fields
}

TokenSet holds the resolved bearer token values keyed by token name. Lookups are O(n) over a small list — Apply API token counts are tiny.

func LoadTokens

func LoadTokens(ctx context.Context, tokens []orktypes.ApplyAPIToken, kube kubeclient.KubeClient, ownNamespace string) (*TokenSet, error)

LoadTokens resolves all Apply API token entries from the Katalog config. secretRef entries are bootstrapped/rotated via the Kubernetes client. token entries are expanded from environment variables.

func (*TokenSet) Matches

func (ts *TokenSet) Matches(value string) string

Matches returns the token name if value is a known token, empty string otherwise.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL