diff --git a/docs/concepts/developer-abstractions.md b/docs/concepts/developer-abstractions.md index 1e39b786..92e3fcfa 100644 --- a/docs/concepts/developer-abstractions.md +++ b/docs/concepts/developer-abstractions.md @@ -26,6 +26,18 @@ The project boundary also defines the scope for internal communication and share can communicate freely with each other. This locality principle reduces complexity for developers while maintaining security and isolation between different application domains. +Every Project references a **ProjectType** (or cluster-scoped **ClusterProjectType**) through `spec.type`. The +project type is the platform engineer's infrastructure template for the project: it defines the data-plane namespace +and the shared resources (network policies, quotas, baseline RBAC) materialized in every environment the project is +deployed to. Projects supply `parameters` that conform to the type's schema, mirroring how Components supply +parameters to their ComponentType. The reference is immutable after creation, and projects created through the API or +the Backstage UI default to the platform's `default` ClusterProjectType when no type is chosen. + +Projects follow the same release lifecycle as components. The Project controller cuts an immutable **ProjectRelease** +whenever the referenced type or the parameter values change, and one **ProjectReleaseBinding** per environment pins a +release to that environment. Promotion at the project level means advancing a binding's pin to a newer release; the +release itself never changes. + ## Component A **Component** represents a deployable unit of software - the fundamental building block of applications in OpenChoreo. diff --git a/docs/concepts/platform-abstractions.md b/docs/concepts/platform-abstractions.md index 3f1ee9f5..630e7b55 100644 --- a/docs/concepts/platform-abstractions.md +++ b/docs/concepts/platform-abstractions.md @@ -134,6 +134,24 @@ When a ResourceReleaseBinding is created, the binding controller renders the Res Resource parameters and environment overrides, produces a RenderedRelease that is applied to the target plane, and resolves the declared outputs so consuming workloads can read them. +## ProjectReleaseBinding + +A **ProjectReleaseBinding** is the project-level member of the binding family. It pins a **ProjectRelease** (an +immutable snapshot of a Project and its referenced ProjectType) to an Environment, and it is the explicit owner of the +project's data-plane namespace for that environment. The binding controller creates the namespace, applies the +namespace-scoped resources rendered from the project type (NetworkPolicies, ResourceQuotas, baseline RBAC), and +reports readiness. Components and resources belonging to the project deploy into that namespace. + +Bindings are authored by clients: the Backstage UI and the `occ` CLI create one binding per pipeline environment at +project-creation time, and GitOps setups commit them to Git. The controller never creates bindings on its own, so a +project only deploys to the environments that have bindings. A binding created without a release pin is seeded once +with the project's latest release; after that, advancing the pin is always an explicit promotion step. + +Like the other bindings, a ProjectReleaseBinding carries per-environment configuration. Values supplied through its +`environmentConfigs` are validated against the project type schema inlined in the pinned ProjectRelease snapshot, not +the live ProjectType, so validation stays fixed for an existing binding even if the type is edited later. This lets the +same release run with different quotas or policies in each environment while the snapshot itself remains unchanged. + ## Component Types A **ComponentType** is a platform engineer-defined template that governs how components are deployed and managed in @@ -262,6 +280,32 @@ The **`retainPolicy`** field on a ResourceType sets the default retention behavi default is `Delete`. Platform engineers opt into `Retain` for templates that emit non-recoverable state (production databases, persistent volumes), and individual environments can still override the policy through the binding. +## Project Types + +A **ProjectType** is a platform engineer-defined template for project-scoped infrastructure. Where ComponentTypes +describe how a code component is deployed and ResourceTypes describe how managed infrastructure is provisioned, +ProjectTypes describe what every project of that type gets in each environment: the data-plane namespace itself and +the shared resources seeded into it, such as default-deny NetworkPolicies, ResourceQuotas, baseline RBAC, and +ImagePullSecrets. This gives infrastructure that is scoped to a project and environment pair a proper home, instead of +being carried by whichever component's template happened to include it. + +OpenChoreo also provides **ClusterProjectType**, a cluster-scoped variant available to projects in all namespaces. The +platform ships a minimal `default` ClusterProjectType that provisions only the namespace; projects created through the +API or the Backstage UI reference it unless another type is chosen. + +ProjectTypes use the same schema-driven approach as ComponentTypes and ResourceTypes. **Parameters** capture +project-level values supplied on the Project and frozen into each ProjectRelease snapshot. **EnvironmentConfigs** +capture per-environment values supplied through ProjectReleaseBinding, so the same release can apply different quotas +or policies in each environment. CEL-based **validation rules** enforce invariants across both during rendering. + +Every ProjectType must include a `v1/Namespace` entry in its resource templates whose name is the platform-computed +namespace placeholder. This mandate is what makes namespace ownership explicit: the namespace exists because the +project was deployed to the environment, with a lifecycle independent of any individual component. + +When a Project references a ProjectType, the Project controller cuts an immutable **ProjectRelease** capturing the +type's spec and the project's parameters at that point in time. Later edits to the ProjectType never affect releases +that were already cut; they produce a new release that environments adopt through promotion. + ## Workflow and ClusterWorkflow A **Workflow** is a platform engineer-defined template for running automation tasks in OpenChoreo. Workflows provide diff --git a/docs/concepts/resource-relationships.md b/docs/concepts/resource-relationships.md index ef832746..66b403b6 100644 --- a/docs/concepts/resource-relationships.md +++ b/docs/concepts/resource-relationships.md @@ -134,6 +134,35 @@ Component chain. The Resource controller never authors bindings on its own; prom is always an explicit action by a platform engineer or GitOps tooling. This isolation keeps developer-facing Resource spec edits from triggering automatic redeploys against stateful infrastructure. +### Project and ProjectType + +**ProjectTypes** define platform-level templates for project-scoped infrastructure: the data-plane namespace and the +shared resources seeded into it in every environment. The cluster-scoped variant **ClusterProjectType** is available +across all namespaces, and the platform ships a minimal `default` ClusterProjectType. + +**Projects** reference a ProjectType (or ClusterProjectType) through `spec.type` and supply parameter values that +conform to the type's schema. The reference is required and immutable, mirroring how Components reference +ComponentTypes: every project is governed by an infrastructure template the platform team has approved, and a project +cannot be re-targeted to a different template after creation. + +### ProjectRelease and ProjectReleaseBinding + +A **ProjectRelease** is an immutable snapshot of a Project and its referenced ProjectType at a specific point in time. +The Project controller cuts a new release whenever the type's spec or the project's parameters change; existing +releases remain unchanged so the same snapshot can be promoted across environments without drift. This mirrors how +ComponentRelease and ResourceRelease work for their domains. + +A **ProjectReleaseBinding** pins a ProjectRelease to an Environment and carries per-environment configuration +overrides. It is also the explicit owner of the project's data-plane namespace for that environment: the binding +controller creates the namespace, applies the resources rendered from the inlined project type, and produces a +**RenderedRelease** applied to the target data plane. Component and resource deployments into an environment depend on +this namespace existing, so the project binding converges first. + +The chain (Project → ProjectRelease → ProjectReleaseBinding → RenderedRelease) completes the pattern at the project +level. Bindings are authored by clients (the Backstage UI, the `occ` CLI, or GitOps commits), not by the controller. A +binding created without a release pin is seeded once with the project's latest release; advancing the pin afterwards +is always an explicit promotion step. + ## Network Relationships Network relationships in OpenChoreo define how components communicate and expose functionality. These relationships @@ -201,10 +230,11 @@ template. Since ComponentReleases are immutable snapshots, existing deployments ### Deletion Cascades Resource relationships define deletion behavior. When a project is deleted, all its components and Resources are -removed. When a Component is deleted, its WorkflowRuns, ComponentReleases, ReleaseBindings, and RenderedReleases are -cleaned up. When a Resource is deleted, deletion is blocked while any ResourceReleaseBinding still references it; once -all bindings are gone, the owned ResourceReleases are removed. These cascading relationships ensure that resources are -properly cleaned up without leaving orphaned objects. +removed, and the project's cleanup finalizer cascades deletion to its ProjectReleaseBindings, tearing down the +data-plane namespaces the bindings own. When a Component is deleted, its WorkflowRuns, ComponentReleases, +ReleaseBindings, and RenderedReleases are cleaned up. When a Resource is deleted, deletion is blocked while any +ResourceReleaseBinding still references it; once all bindings are gone, the owned ResourceReleases are removed. These +cascading relationships ensure that resources are properly cleaned up without leaving orphaned objects. The retention of the underlying data-plane state on a binding deletion is governed by the binding's retention policy. Production environments can preserve volumes and databases on deletion while dev and staging dispose of them, the same diff --git a/docs/concepts/runtime-model.md b/docs/concepts/runtime-model.md index 8ad5a2e8..0905643f 100644 --- a/docs/concepts/runtime-model.md +++ b/docs/concepts/runtime-model.md @@ -21,6 +21,13 @@ its own namespace, network policies, and security boundaries. Components within cluster-local networking, but all communication across cell boundaries must flow through well-defined gateways. This architecture ensures that failures, security breaches, or performance issues in one cell cannot directly impact others. +The cell's namespace and its baseline resources have an explicit owner: the **ProjectReleaseBinding** for that project +and environment. The binding renders the project's ProjectType (which must declare the namespace itself as a template +entry), creates the namespace on the data plane, and seeds it with the shared infrastructure the type defines, such as +default-deny network policies and resource quotas. Component and resource deployments into the environment wait for +this namespace to exist, so the cell's boundary is established before anything runs inside it, and its lifecycle is +tied to the project and environment rather than to whichever component deployed first. + The cell model aligns with microservices best practices and Domain-Driven Design principles. By mapping bounded contexts to isolated runtime units, OpenChoreo ensures that architectural boundaries are enforced by infrastructure. This alignment reduces the cognitive load on developers - the same mental model used for designing applications applies to diff --git a/docs/developer-guide/deploying-applications/project-releases.md b/docs/developer-guide/deploying-applications/project-releases.md new file mode 100644 index 00000000..e3fcd404 --- /dev/null +++ b/docs/developer-guide/deploying-applications/project-releases.md @@ -0,0 +1,167 @@ +--- +title: Project Releases +description: How project releases are cut, bound to environments, and promoted +--- + +# Project Releases + +Components are not the only thing a project deploys. Every project also carries project-scoped infrastructure (its data-plane namespace, network policies, quotas) defined by the [ProjectType](../../platform-engineer-guide/project-types.md) it references. This page covers how that infrastructure is versioned and promoted through project releases. + +Two resources drive the lifecycle: + +- **ProjectRelease**: an immutable snapshot of your project's type and parameter values at a point in time. Cut automatically; you never edit one. +- **ProjectReleaseBinding**: one per environment. It pins a ProjectRelease to that environment, owns the project's data-plane namespace there, and carries per-environment configuration. + +```text +Project + ProjectType → ProjectRelease → ProjectReleaseBinding (per environment) → data plane +``` + +This is the same pattern components follow with ComponentRelease and ReleaseBinding, applied at the project level. + +## Setting the Project Type + +Every Project references a ProjectType or ClusterProjectType through `spec.type`, and optionally supplies `parameters` that conform to the type's schema: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: Project +metadata: + name: online-store + namespace: default +spec: + deploymentPipelineRef: + name: default + type: + kind: ClusterProjectType + name: standard-project + parameters: + tier: premium +``` + +`spec.type` is immutable after creation. When you create a project through the Backstage UI or the OpenChoreo API without picking a type, it defaults to the platform's `default` ClusterProjectType, which provisions only the namespace. Projects applied directly with `kubectl` must set `spec.type` explicitly. + +To generate a Project manifest from a type's schema, with required parameters as placeholders and optional ones as commented examples, use the scaffolder: + +```bash +occ project scaffold online-store --clusterprojecttype standard-project +``` + +The scaffold also emits one ProjectReleaseBinding per environment in the deployment pipeline (`--no-bindings` opts out). + +## How Releases Are Cut + +You do not create project releases by hand. The Project controller watches the project and its referenced type, and cuts a new ProjectRelease named `-` whenever either changes: + +- The platform engineer updates the ProjectType (a new policy, a changed template) +- You change `spec.parameters` on the Project + +The newest release is recorded on the project's status: + +```bash +kubectl get project online-store -o jsonpath='{.status.latestRelease.name}' +# online-store-a1b2c3d4 +``` + +Existing releases are never modified. Environments that pin an older release keep running it until you promote. + +## Bindings and Where They Come From + +Each environment gets exactly one ProjectReleaseBinding. When you create a project through the Backstage UI or scaffold it with `occ`, one binding per pipeline environment is created for you. In GitOps setups you commit the bindings yourself; the controller never creates them behind your back, so a project only deploys to the environments you have bindings for. + +A binding created with an empty `spec.projectRelease` is seeded once by the Project controller with the project's latest release. After that, no controller touches the pin. Advancing it is always an explicit action: that is the promotion model. + +## Deploy and Promote + +### Via CLI + +`occ project deploy` follows the same shape as `occ component deploy`, one verb for both deploy and promote: + +```bash +# Deploy: ensure a binding for the first pipeline environment. +# The pin is left unset so the controller seeds it with the latest release. +occ project deploy online-store + +# Promote: advance the staging binding to the release development runs +occ project deploy online-store --to staging + +# Promote to production, setting per-environment values on the way +occ project deploy online-store --to production --set cpuQuota=16 +``` + +`--set key=value` merges into the binding's `environmentConfigs`. + +### Via Backstage UI + +1. Open your project in the Backstage console +2. Go to the **Releases** view to see the releases and what each environment currently runs +3. Click **Promote** on the target environment and confirm; the UI shows a diff against what the environment runs today + +### Via kubectl or GitOps + +Promotion is a one-field patch on the target environment's binding: + +```bash +kubectl patch projectreleasebinding online-store-staging \ + --type merge -p '{"spec":{"projectRelease":"online-store-a1b2c3d4"}}' +``` + +In GitOps mode, commit the same change to the binding manifest in Git. Manifests that omit `projectRelease` never conflict with the controller's one-time seeding under server-side apply, so both styles coexist: leave the field out to let the platform seed the first environment, and manage promotion pins explicitly where you want Git to be authoritative. + +## Environment-Specific Configuration + +The ProjectType declares an `environmentConfigs` schema; the binding supplies the values for its environment: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: online-store-production + namespace: default +spec: + owner: + projectName: online-store + environment: production + projectRelease: online-store-a1b2c3d4 + environmentConfigs: + cpuQuota: "16" + memoryQuota: "32Gi" +``` + +The same release deployed to development and production can run with different quotas or policies while the snapshot itself stays identical. Values are validated against the schema on the pinned release; a value the schema rejects surfaces on the binding's conditions rather than reaching the data plane. + +## Rolling Back + +Because releases are immutable and kept, rollback is the same operation as promotion: point the binding at an older release. + +```bash +occ projectrelease list --project online-store +kubectl patch projectreleasebinding online-store-production \ + --type merge -p '{"spec":{"projectRelease":"online-store-9f8e7d6c"}}' +``` + +## What Happens on the Data Plane + +The binding owns the project's data-plane namespace for its environment (named `dp-{namespace}-{project}-{environment}-{hash}` and surfaced on `status.namespace`). It creates the namespace, applies the resources rendered from the project type, and reports readiness through conditions: + +| Condition | Meaning | +| ---------------- | ----------------------------------------------------- | +| `Synced` | The pinned release resolved and rendered successfully | +| `NamespaceReady` | The data-plane namespace exists and is active | +| `ResourcesReady` | The rendered project-type resources report healthy | +| `Ready` | Aggregate over the three above | + +Your components and resources deploy into that namespace. Until the project binding for an environment has converged, component and resource deployments to that environment wait for the namespace to exist. + +```bash +kubectl get prb -n default +# NAME PROJECT ENVIRONMENT RELEASE READY AGE +# online-store-development online-store development online-store-a1b2c3d4 True 2d +# online-store-staging online-store staging online-store-a1b2c3d4 True 1d +``` + +## What's Next + +- [Deploy and Promote](./deploy-and-promote.md): the component-level deployment workflow +- [Environment Overrides](./environment-overrides.md): per-environment configuration for components +- [Authoring ProjectTypes](../../platform-engineer-guide/project-types.md): the platform engineer side of this lifecycle +- [ProjectRelease API Reference](../../reference/api/runtime/projectrelease.md) and [ProjectReleaseBinding API Reference](../../reference/api/platform/projectreleasebinding.md) diff --git a/docs/developer-guide/projects-and-components/creating-a-project.md b/docs/developer-guide/projects-and-components/creating-a-project.md index 02e803b6..2eab4e00 100644 --- a/docs/developer-guide/projects-and-components/creating-a-project.md +++ b/docs/developer-guide/projects-and-components/creating-a-project.md @@ -11,12 +11,14 @@ A Project is a logical boundary that groups related components together. Each pr A Project resource is intentionally minimal: -| Field | Description | -| ---------------------------- | --------------------------------------------------------------------------------------------------- | -| `metadata.name` | Unique name within the namespace (must be a valid Kubernetes name) | -| `metadata.namespace` | The namespace this project belongs to | -| `spec.deploymentPipelineRef` | Reference to a DeploymentPipeline that controls environment promotion | -| Annotations | Optional `openchoreo.dev/display-name` and `openchoreo.dev/description` for human-readable metadata | +| Field | Description | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `metadata.name` | Unique name within the namespace (must be a valid Kubernetes name) | +| `metadata.namespace` | The namespace this project belongs to | +| `spec.deploymentPipelineRef` | Reference to a DeploymentPipeline that controls environment promotion | +| `spec.type` | Reference to a ProjectType or ClusterProjectType that defines the project's infrastructure template. Immutable after creation | +| `spec.parameters` | Optional values conforming to the referenced project type's parameter schema | +| Annotations | Optional `openchoreo.dev/display-name` and `openchoreo.dev/description` for human-readable metadata | ## Creating via Backstage UI @@ -49,6 +51,21 @@ spec: deploymentPipelineRef: kind: DeploymentPipeline name: default + type: + kind: ClusterProjectType + name: default +``` + +`spec.type` references the [ProjectType](../../platform-engineer-guide/project-types.md) that defines the project's +infrastructure template and is required when applying manifests directly. Projects created through the OpenChoreo API +or the Backstage UI default to the platform's `default` ClusterProjectType when no type is chosen. The field is +immutable after creation. If the type declares a parameter schema, supply values through `spec.parameters`. + +You can also generate a Project manifest (and one ProjectReleaseBinding per pipeline environment) from a type's +schema: + +```bash +occ project scaffold online-store --clusterprojecttype default ``` Apply it: @@ -98,3 +115,4 @@ In the Backstage UI, click the edit icon on the Deployment Pipeline card on the ## What's Next - [Creating a Component](./creating-a-component.md): deploy a service, web app, or scheduled task within your project +- [Project Releases](../deploying-applications/project-releases.md): how the project's infrastructure is versioned, bound to environments, and promoted diff --git a/docs/platform-engineer-guide/project-types.md b/docs/platform-engineer-guide/project-types.md new file mode 100644 index 00000000..6a0e942a --- /dev/null +++ b/docs/platform-engineer-guide/project-types.md @@ -0,0 +1,277 @@ +--- +title: Authoring ProjectTypes +description: Learn how to author ProjectTypes and ClusterProjectTypes in OpenChoreo +--- + +# Authoring ProjectTypes + +This guide covers how to create custom [ProjectTypes](../reference/api/platform/projecttype.md) and [ClusterProjectTypes](../reference/api/platform/clusterprojecttype.md) in OpenChoreo. A ProjectType is the platform-engineer-defined infrastructure template for a project: it declares the namespace-scoped Kubernetes resources the platform materializes in every environment the project is deployed to. + +## What is a ProjectType? + +A ProjectType plays the same role for project-scoped infrastructure that a ComponentType plays for code components and a ResourceType plays for managed infrastructure. Components and Resources describe things owned by a single deployable unit. A ProjectType describes what is shared by everything in a project within one environment: the data-plane namespace itself, NetworkPolicies, ResourceQuotas, baseline RBAC, ImagePullSecrets, and similar cross-cutting concerns. + +Before project types, this shared infrastructure had no proper home. It had to be carried by a ComponentType's resource templates or a Trait, tying it to whichever component happened to include it. A ProjectType gives project-scoped infrastructure its own template, its own release lifecycle, and an explicit owner for the namespace. + +Platform engineers use ProjectTypes to: + +- Own the cell namespace (`dp-{ns}-{project}-{env}-{hash}`) for each project and environment pair +- Seed every cell namespace with baseline policies: default-deny NetworkPolicies, ResourceQuotas, RBAC, ImagePullSecrets +- Define what project authors can configure through a `parameters` schema +- Define per-environment knobs through an `environmentConfigs` schema +- Enforce invariants with CEL validation rules + +Developers reference a ProjectType from `Project.spec.type` and supply parameter values. The Project controller cuts an immutable [ProjectRelease](../reference/api/runtime/projectrelease.md) that inlines the type's spec at that point in time, and each [ProjectReleaseBinding](../reference/api/platform/projectreleasebinding.md) renders the inlined templates per environment and applies the result to the data plane. + +OpenChoreo ships a minimal `default` ClusterProjectType (under `samples/getting-started/cluster-project-types/`) that provisions only the cell namespace. When a project is created through the OpenChoreo API or the Backstage UI without an explicit type, it defaults to this ClusterProjectType. Projects applied directly with `kubectl` must set `spec.type` themselves, since the field is required on the CRD. + +### ClusterProjectType + +A **ClusterProjectType** is the cluster-scoped variant of ProjectType. Use it for templates intended to be shared platform-wide; namespace-scoped ProjectTypes are available when a template should only be visible within a specific namespace. + +ClusterProjectTypes share the same spec structure as ProjectTypes; only scope differs. + +**Key concepts:** + +- `parameters` / `environmentConfigs`: define what project authors can configure on the Project, and what bindings can override per environment +- `validations`: CEL rules evaluated during rendering; all must pass for rendering to proceed +- `resources`: namespace-scoped Kubernetes manifest templates applied to the cell namespace (rendered through CEL) + +## The Namespace Mandate + +Every ProjectType's `spec.resources` must include a `v1/Namespace` entry whose `metadata.name` is the literal `${metadata.namespace}` placeholder. This is the cell namespace: the data-plane namespace where all of the project's components and resources run for a given environment. Making it a mandated template entry, rather than an implicit side effect, is what gives the namespace an explicit owner (the ProjectReleaseBinding) and a lifecycle tied to the project and environment rather than to whichever component deployed first. + +```yaml +resources: + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${metadata.labels} +``` + +The mandate is enforced against the rendered output, not the spec. If the entry is missing, or an `includeWhen` guard suppresses it, the binding reports `Synced=False` with reason `NamespaceMissing` and nothing is applied. + +## ProjectType Example + +The example below defines a `standard-project` ClusterProjectType that owns the cell namespace, applies a default-deny-egress NetworkPolicy, and adds a conditional egress exception driven by a per-environment override. + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ClusterProjectType +metadata: + name: standard-project +spec: + # Project-author-facing values; captured in the ProjectRelease snapshot. + parameters: + openAPIV3Schema: + type: object + properties: + tier: + type: string + enum: [standard, premium] + default: standard + + # Per-environment overrides applied through ProjectReleaseBinding. + environmentConfigs: + openAPIV3Schema: + type: object + properties: + cpuQuota: + type: string + default: "4" + memoryQuota: + type: string + default: "8Gi" + allowMonitoringEgress: + type: boolean + default: false + + # CEL rules evaluated during rendering. All must be true. + validations: + - rule: "${environmentConfigs.cpuQuota.matches('^[0-9]+$')}" + message: 'cpuQuota must be an integer string (e.g. "4", "8")' + + resources: + # Mandated cell-namespace entry. metadata.name MUST be the literal + # ${metadata.namespace} placeholder. + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${metadata.labels} + + - id: resource-quota + template: + apiVersion: v1 + kind: ResourceQuota + metadata: + name: project-quota + namespace: ${metadata.namespace} + labels: ${metadata.labels} + spec: + hard: + limits.cpu: ${environmentConfigs.cpuQuota} + limits.memory: ${environmentConfigs.memoryQuota} + + - id: default-deny-egress + template: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny-egress + namespace: ${metadata.namespace} + labels: ${metadata.labels} + spec: + podSelector: {} + policyTypes: [Egress] + egress: [] + + # Conditional entry, included only when the per-env override flips + # allowMonitoringEgress to true. + - id: allow-monitoring-egress + includeWhen: "${environmentConfigs.allowMonitoringEgress}" + template: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-monitoring-egress + namespace: ${metadata.namespace} + labels: ${metadata.labels} + spec: + podSelector: {} + policyTypes: [Egress] + egress: + - to: + - namespaceSelector: + matchLabels: + name: monitoring +``` + +Every non-namespace entry sets its own `metadata.namespace` from `${metadata.namespace}`, so all rendered objects land in the cell namespace the same template creates. + +## Parameters and EnvironmentConfigs + +ProjectTypes use the same two-schema approach as ComponentTypes and ResourceTypes: + +**Parameters** capture values supplied by the project author on `Project.spec.parameters`. They are validated against the `parameters` schema and inlined into every ProjectRelease snapshot, so the same values apply wherever that release is deployed. Changing a parameter on the Project cuts a new release; existing releases are never modified. + +**EnvironmentConfigs** capture per-environment values supplied on `ProjectReleaseBinding.spec.environmentConfigs`. They are validated against the schema on the pinned release by the binding controller, and validation failures surface through the binding's `status.conditions`. Because they live on the binding, the same release deployed to dev and production can use different quotas, labels, or feature flips while the snapshot itself is unchanged. + +Both schemas use standard [OpenAPI V3 JSON Schema](https://swagger.io/specification/) (`openAPIV3Schema`) with support for defaults, enums, and validation constraints. + +## Validations + +The `validations` section holds CEL rules evaluated during rendering. Each rule is a `${...}`-wrapped boolean expression paired with an error message: + +```yaml +validations: + - rule: "${environmentConfigs.cpuQuota.matches('^[0-9]+$')}" + message: "cpuQuota must be an integer string" +``` + +All rules must evaluate to true for rendering to proceed. Rules have access to the same CEL context as resource templates, so they can validate combinations across `parameters` and `environmentConfigs` that a plain schema cannot express. A failing rule blocks the render and surfaces the message on the binding's conditions. + +See [Validation Rules](./component-types/validation-rules.md) for the shared syntax. + +## CEL Surface for Project Templates + +Resource templates, `includeWhen` guards, and validation rules have access to the following CEL context: + +| Context | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `metadata.*` | Platform-injected surface: `namespace` (the cell namespace), `projectNamespace`, `projectName`, `environmentName`, `dataPlaneName`, project/environment/data-plane UIDs, `labels`, `annotations` | +| `parameters.*` | Values from `Project.spec.parameters` after schema defaulting, as captured in the pinned ProjectRelease | +| `environmentConfigs.*` | Values from `ProjectReleaseBinding.spec.environmentConfigs` after schema defaulting | +| `environment.*` | Per-environment surface, including the merged effective gateway for this environment | +| `dataplane.*` | Target DataPlane attributes | +| `gateway.*` | Effective gateway (Environment-level override merged onto the DataPlane-level default) | + +A few notes: + +- `${metadata.namespace}` is the platform-computed `dp-{ns}-{project}-{env}-{hash}` cell namespace. The mandated Namespace entry uses it as `metadata.name`; every other entry uses it as `metadata.namespace`. +- Templates that may evaluate against a missing gateway must guard with `has(environment.gateway)`; `has(gateway)` is invalid CEL because the top-level alias is omitted when no gateway is configured. +- There is no `applied.*` context and no `readyWhen` or `outputs` on project types. Readiness of the rendered objects is evaluated through the per-Kind health heuristics in `RenderedRelease` and surfaces on the binding's `ResourcesReady` condition. + +## includeWhen and forEach + +Each resource template entry supports the same conditional and iterative fields as ComponentTypes: + +**`includeWhen`** is a boolean CEL expression evaluated at render time. When it returns `false`, the entry is omitted from the rendered output and any previously applied object is garbage-collected from the data plane. Do not guard the mandated Namespace entry; a suppressed namespace trips the `NamespaceMissing` check. + +**`forEach`** iterates over a list to generate multiple objects from one template, with `var` naming the loop variable. Useful for emitting one object per item in a list-typed parameter, for example a NetworkPolicy per allowed egress target. + +## How Developers Consume a ProjectType + +Project authors reference the type from `Project.spec.type` and supply parameter values that conform to the declared schema: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: Project +metadata: + name: online-store + namespace: default +spec: + deploymentPipelineRef: + name: default + type: + kind: ClusterProjectType + name: standard-project + parameters: + tier: premium +``` + +`spec.type` is immutable: a project cannot be re-targeted to a different type after creation. The Project controller cuts a ProjectRelease named `-` whenever the inlined type snapshot or the parameter values change, and records the newest one on `status.latestRelease`. + +Each environment gets one ProjectReleaseBinding, which carries the per-environment values: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: online-store-production + namespace: default +spec: + owner: + projectName: online-store + environment: production + projectRelease: online-store-a1b2c3d4 + environmentConfigs: + cpuQuota: "16" + memoryQuota: "32Gi" +``` + +The binding controller creates the cell namespace, applies the rendered project-type resources to it, and reports readiness through `status.conditions`. Components and resources belonging to the project deploy into that namespace and wait for it to exist. See the [Project Releases developer guide](../developer-guide/deploying-applications/project-releases.md) for the binding and promotion workflow. + +## Syntax Systems + +ProjectTypes reuse the same syntax systems documented in the Component Types guide: + +| Syntax | Purpose | Used In | +| --------------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------- | +| [Templating](./component-types/templating-syntax.md) | Dynamic value generation using CEL expressions | `resources[].template`, `includeWhen`, `forEach` | +| [Schema](./component-types/schema-syntax.md) | Parameter validation and defaults | `parameters.openAPIV3Schema` and `environmentConfigs.openAPIV3Schema` | +| [Validation Rules](./component-types/validation-rules.md) | Cross-field CEL validation | `validations[]` | + +Patching (Trait-only) and outputs (ResourceType-only) do not apply to ProjectTypes. + +## CEL Reference + +- **[Context Variables](../reference/cel/context-variables.md)**: `metadata`, `parameters`, `environmentConfigs`, `environment`, `dataplane`, `gateway` +- **[Built-in Functions](../reference/cel/built-in-functions.md)**: `oc_omit()`, `oc_merge()`, `oc_generate_name()`, `oc_dns_label()` + +## Next Steps + +- **[Project Releases (developer guide)](../developer-guide/deploying-applications/project-releases.md)**: how releases are cut, bound to environments, and promoted + +## Related Resources + +- [ProjectType API Reference](../reference/api/platform/projecttype.md): full CRD specification +- [ClusterProjectType API Reference](../reference/api/platform/clusterprojecttype.md): cluster-scoped variant +- [ProjectRelease API Reference](../reference/api/runtime/projectrelease.md): immutable snapshot +- [ProjectReleaseBinding API Reference](../reference/api/platform/projectreleasebinding.md): per-environment binding +- [Project API Reference](../reference/api/application/project.md): developer-facing CRD diff --git a/docs/reference/api/application/project.md b/docs/reference/api/application/project.md index 5ed1aa3b..0c209362 100644 --- a/docs/reference/api/application/project.md +++ b/docs/reference/api/application/project.md @@ -32,6 +32,12 @@ metadata: | Field | Type | Required | Default | Description | | ----------------------- | ----------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `deploymentPipelineRef` | [DeploymentPipelineRef](#deploymentpipelineref) | Yes | - | Reference to the DeploymentPipeline that defines the promotion paths between environments for this project. Must reference an existing DeploymentPipeline in the same namespace | +| `type` | [ProjectTypeRef](#projecttyperef) | Yes | - | Reference to the (Cluster)ProjectType that defines the infrastructure template materialized in each environment's data-plane namespace (immutable) | +| `parameters` | object | No | - | Project-level values validated against the referenced type's `parameters` schema and inlined into each ProjectRelease snapshot | + +:::note +`type` is immutable after creation. The Project controller automatically cuts a new [ProjectRelease](../runtime/projectrelease.md) whenever the inlined type snapshot or the `parameters` values change. Projects created through the OpenChoreo API or the Backstage UI default `type` to the platform's `default` ClusterProjectType when no type is chosen; manifests applied directly must set it. +::: ### DeploymentPipelineRef @@ -42,12 +48,31 @@ Reference to a DeploymentPipeline that defines the promotion paths between envir | `kind` | string | No | `DeploymentPipeline` | Kind of the deployment pipeline resource | | `name` | string | Yes | - | Name of the deployment pipeline resource | +### ProjectTypeRef + +Reference to the project's infrastructure template. + +| Field | Type | Required | Default | Description | +| ------ | ------ | -------- | ------------- | ---------------------------------------------------- | +| `kind` | string | No | `ProjectType` | `ProjectType` or `ClusterProjectType` | +| `name` | string | Yes | - | Name of the referenced type (DNS-1123 label, min: 1) | + ### Status Fields -| Field | Type | Default | Description | -| -------------------- | ----------- | ------- | --------------------------------------------------------- | -| `observedGeneration` | integer | 0 | The generation observed by the controller | -| `conditions` | []Condition | [] | Standard Kubernetes conditions tracking the project state | +| Field | Type | Default | Description | +| -------------------- | --------------------------------------------- | ------- | --------------------------------------------------------- | +| `observedGeneration` | integer | 0 | The generation observed by the controller | +| `conditions` | []Condition | [] | Standard Kubernetes conditions tracking the project state | +| `latestRelease` | [LatestProjectRelease](#latestprojectrelease) | - | The most recent ProjectRelease cut for this project | + +#### LatestProjectRelease + +ProjectReleaseBindings pin `spec.projectRelease` to the name recorded here (or to an older release for rollback). + +| Field | Type | Description | +| ------ | ------ | ----------------------------------------------------------------------------------------- | +| `name` | string | Name of the latest ProjectRelease (`-`) | +| `hash` | string | Spec hash that produced the release; a new release is cut when the recomputed hash drifts | #### Condition Types @@ -75,6 +100,27 @@ metadata: spec: deploymentPipelineRef: name: default-deployment-pipeline + type: + kind: ClusterProjectType + name: default +``` + +### Project With a Custom Type and Parameters + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: Project +metadata: + name: online-store + namespace: default +spec: + deploymentPipelineRef: + name: default + type: + kind: ClusterProjectType + name: standard-project + parameters: + tier: premium ``` ## Annotations @@ -90,3 +136,6 @@ Projects support the following annotations: - [Component](./component.md) - Deployable units within projects - [DeploymentPipeline](../platform/deployment-pipeline.md) - Defines environment promotion paths +- [ProjectType](../platform/projecttype.md) / [ClusterProjectType](../platform/clusterprojecttype.md) - Infrastructure template referenced by `spec.type` +- [ProjectRelease](../runtime/projectrelease.md) - Immutable snapshot cut by the Project controller +- [ProjectReleaseBinding](../platform/projectreleasebinding.md) - Pins a release to an environment diff --git a/docs/reference/api/platform/clusterprojecttype.md b/docs/reference/api/platform/clusterprojecttype.md new file mode 100644 index 00000000..b25fceb4 --- /dev/null +++ b/docs/reference/api/platform/clusterprojecttype.md @@ -0,0 +1,122 @@ +--- +title: ClusterProjectType API Reference +description: Cluster-scoped project infrastructure template available to projects in all namespaces +--- + +# ClusterProjectType + +A ClusterProjectType is the cluster-scoped sibling of [ProjectType](./projecttype.md). Projects in any namespace can reference a ClusterProjectType through `Project.spec.type` with `kind: ClusterProjectType`. Use it for infrastructure templates intended to be shared platform-wide; use a namespace-scoped ProjectType when a template should only be visible within a single namespace. + +The spec structure is identical to ProjectType; only the scope differs. + +## API Version + +`openchoreo.dev/v1alpha1` + +## Resource Definition + +### Metadata + +ClusterProjectTypes are cluster-scoped resources and carry no namespace. + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ClusterProjectType +metadata: + name: +``` + +**Short names:** `cpt`, `cpts` + +### Spec Fields + +The spec is field-for-field identical to [ProjectTypeSpec](./projecttype.md#spec-fields): + +| Field | Type | Required | Description | +| -------------------- | ------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | +| `parameters` | [SchemaSection](./projecttype.md#schemasection) | No | Schema for the values project authors supply on `Project.spec.parameters` | +| `environmentConfigs` | [SchemaSection](./projecttype.md#schemasection) | No | Schema for the per-environment values on `ProjectReleaseBinding.spec.environmentConfigs` | +| `validations` | [[ValidationRule](./projecttype.md#validationrule)] | No | CEL rules evaluated during rendering; all must evaluate to true | +| `resources` | [[ResourceTemplate](./projecttype.md#resourcetemplate)] | Yes | Namespace-scoped manifest templates applied to the cell namespace (min: 1). Must include the mandated `v1/Namespace` entry | + +### Status Fields + +ClusterProjectType does not report status fields. + +## The Default ClusterProjectType + +OpenChoreo ships a `default` ClusterProjectType that provisions only the cell namespace. When a project is created through the OpenChoreo API or the Backstage UI without an explicit type, `spec.type` defaults to it. Its `environmentConfigs` schema accepts `namespaceLabels` and `namespaceAnnotations` maps, so per-environment labels and annotations can be added to the namespace through the binding: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ClusterProjectType +metadata: + name: default +spec: + environmentConfigs: + openAPIV3Schema: + type: object + properties: + namespaceLabels: + type: object + additionalProperties: + type: string + default: {} + namespaceAnnotations: + type: object + additionalProperties: + type: string + default: {} + resources: + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${oc_merge(metadata.labels, environmentConfigs.namespaceLabels)} + annotations: ${environmentConfigs.namespaceAnnotations} +``` + +## Examples + +### Referencing a ClusterProjectType From a Project + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: Project +metadata: + name: online-store + namespace: default +spec: + deploymentPipelineRef: + name: default + type: + kind: ClusterProjectType + name: standard-project + parameters: + tier: premium +``` + +## Usage + +```bash +# List cluster project types +kubectl get cpt +occ clusterprojecttype list + +# Inspect a cluster project type +kubectl describe clusterprojecttype default +occ clusterprojecttype get default + +# Create or update from a manifest +occ apply -f clusterprojecttype.yaml +``` + +## Related Resources + +- [ProjectType](./projecttype.md): namespace-scoped variant with the full field documentation +- [Project](../application/project.md): references the type through `spec.type` +- [ProjectRelease](../runtime/projectrelease.md): immutable snapshot that inlines the type spec +- [ProjectReleaseBinding](./projectreleasebinding.md): renders the inlined type per environment +- [Authoring ProjectTypes (PE Guide)](../../../platform-engineer-guide/project-types.md) diff --git a/docs/reference/api/platform/projectreleasebinding.md b/docs/reference/api/platform/projectreleasebinding.md new file mode 100644 index 00000000..4294ddcd --- /dev/null +++ b/docs/reference/api/platform/projectreleasebinding.md @@ -0,0 +1,153 @@ +--- +title: ProjectReleaseBinding API Reference +description: Pins a ProjectRelease to an environment, owns the cell namespace, and carries per-environment overrides +--- + +# ProjectReleaseBinding + +A ProjectReleaseBinding pins a [ProjectRelease](../runtime/projectrelease.md) to an [Environment](./environment.md), owns the cell namespace for that project and environment pair, and applies the project-type resources from the inlined snapshot to that namespace. It is the project-level counterpart of [ReleaseBinding](./releasebinding.md) and [ResourceReleaseBinding](./resourcereleasebinding.md). + +Bindings are authored by clients: the Backstage UI and `occ project scaffold` create one binding per pipeline environment at project-creation time, and GitOps setups commit them to Git. The Project controller never creates bindings on its own. It retains one narrow behavior: when a binding's `spec.projectRelease` is empty, the controller seeds it once with the project's latest release. A pin that has been set is never touched by any controller; advancing it is how you promote. + +## API Version + +`openchoreo.dev/v1alpha1` + +## Resource Definition + +### Metadata + +ProjectReleaseBindings are namespace-scoped resources created in the same namespace as the Project they deploy. + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: - + namespace: +``` + +**Short names:** `prb`, `prbs` + +### Spec Fields + +| Field | Type | Required | Default | Description | +| -------------------- | --------------------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `owner` | [ProjectReleaseBindingOwner](#projectreleasebindingowner) | Yes | - | Identifies the project this binding deploys (immutable) | +| `environment` | string | Yes | - | Name of the target Environment (immutable; min: 1) | +| `projectRelease` | string | No | - | Name of the pinned ProjectRelease. When left unset, the Project controller seeds it once with the project's latest release; the binding stays pending until a pin is set | +| `environmentConfigs` | object | No | - | Per-environment values for the inlined type's `environmentConfigs` schema. Validated against the schema on the pinned release by the binding controller | + +:::note +`owner` and `environment` are immutable after creation. To re-target a binding, delete and recreate it. +::: + +### ProjectReleaseBindingOwner + +| Field | Type | Required | Description | +| ------------- | ------ | -------- | ---------------------------- | +| `projectName` | string | Yes | Name of the project (min: 1) | + +### Status Fields + +| Field | Type | Default | Description | +| -------------------- | ----------- | ------- | --------------------------------------------------------------------------------------------------------- | +| `observedGeneration` | integer | 0 | The generation observed by the controller | +| `conditions` | []Condition | [] | Standard Kubernetes conditions tracking binding state | +| `namespace` | string | - | The data-plane namespace owned by this binding (`dp-{ns}-{project}-{env}-{hash}`), surfaced for debugging | + +#### Condition Types + +| Type | Meaning | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Synced` | The binding resolved its pinned ProjectRelease and produced a matching `RenderedRelease`. Reported false with reason `ProjectReleaseNotSet` (no pin yet), `ProjectReleaseNotFound` (pin points at a missing release), or `NamespaceMissing` (rendered output lacks the mandated Namespace) | +| `NamespaceReady` | The project's data-plane namespace exists and is active | +| `ResourcesReady` | Every non-namespace entry rendered from the project type reports healthy | +| `Ready` | Aggregate condition over `Synced`, `NamespaceReady`, and `ResourcesReady` | + +## Examples + +### Basic ProjectReleaseBinding + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: online-store-development + namespace: default +spec: + owner: + projectName: online-store + environment: development + projectRelease: online-store-a1b2c3d4 +``` + +### Binding With Environment-Specific Overrides + +`environmentConfigs` supplies per-environment values declared in the project type's `environmentConfigs` schema: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: online-store-production + namespace: default +spec: + owner: + projectName: online-store + environment: production + projectRelease: online-store-a1b2c3d4 + environmentConfigs: + cpuQuota: "16" + memoryQuota: "32Gi" + allowMonitoringEgress: true +``` + +### Pending Binding (No Release Pinned Yet) + +A binding created without `spec.projectRelease` is seeded once by the Project controller with the project's latest release. If no release exists yet, the binding stays `Synced=False, Reason=ProjectReleaseNotSet` until one is cut: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectReleaseBinding +metadata: + name: online-store-development + namespace: default +spec: + owner: + projectName: online-store + environment: development + # projectRelease intentionally unset; the Project controller seeds it + # once with the latest release, then never touches it again. +``` + +## Promoting a Release + +Advancing `spec.projectRelease` is how a project release moves into an environment. The `occ` CLI advances the target environment's binding to the release pinned in the source environment: + +```bash +# Promote whatever development runs to staging +occ project deploy online-store --to staging + +# Equivalent kubectl flow +kubectl get project online-store -o jsonpath='{.status.latestRelease.name}' +kubectl patch projectreleasebinding online-store-staging \ + --type merge -p '{"spec":{"projectRelease":""}}' +``` + +In GitOps setups, advance the pin with a commit. Manifests that omit `projectRelease` never contend for the field under server-side apply: the controller's seeding only fills an empty pin once, so a Git-managed pin stays authoritative. + +## Deletion + +Deleting a binding tears down what it owns on the data plane, including the cell namespace, through its `RenderedRelease`. When a Project is deleted, its cleanup finalizer cascades the deletion to all of the project's bindings. + +## Related Resources + +- [Project](../application/project.md): owns the binding through `spec.owner.projectName` +- [ProjectRelease](../runtime/projectrelease.md): immutable snapshot pinned by `spec.projectRelease` +- [ProjectType](./projecttype.md) / [ClusterProjectType](./clusterprojecttype.md): source of the `environmentConfigs` schema and the rendered resources +- [Environment](./environment.md): target environment for the binding +- [RenderedRelease](../runtime/renderedrelease.md): final manifests produced by the binding controller +- [ReleaseBinding](./releasebinding.md): component-side counterpart +- [ResourceReleaseBinding](./resourcereleasebinding.md): resource-side counterpart +- [Project Releases (developer guide)](../../../developer-guide/deploying-applications/project-releases.md) diff --git a/docs/reference/api/platform/projecttype.md b/docs/reference/api/platform/projecttype.md new file mode 100644 index 00000000..ec24cc3f --- /dev/null +++ b/docs/reference/api/platform/projecttype.md @@ -0,0 +1,185 @@ +--- +title: ProjectType API Reference +description: Platform template defining the project-scoped infrastructure materialized in each environment's cell namespace +--- + +# ProjectType + +A ProjectType is the platform-engineer-defined infrastructure template for a project. It declares the namespace-scoped Kubernetes resources (the cell namespace itself, NetworkPolicies, ResourceQuotas, baseline RBAC, ImagePullSecrets) that the platform materializes in every environment a project is deployed to, along with the schemas for project-level parameters and per-environment overrides. + +Developers reference a ProjectType from [`Project.spec.type`](../application/project.md). The Project controller inlines the type's spec into every [ProjectRelease](../runtime/projectrelease.md) it cuts, and each [ProjectReleaseBinding](./projectreleasebinding.md) renders the inlined templates for its environment. + +For the cluster-scoped variant, see [ClusterProjectType](./clusterprojecttype.md). + +## API Version + +`openchoreo.dev/v1alpha1` + +## Resource Definition + +### Metadata + +ProjectTypes are namespace-scoped resources. Projects in the same namespace reference them by name. + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectType +metadata: + name: + namespace: +``` + +**Short names:** `pt`, `pts` + +### Spec Fields + +| Field | Type | Required | Default | Description | +| -------------------- | --------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------- | +| `parameters` | [SchemaSection](#schemasection) | No | - | Schema for the values project authors supply on `Project.spec.parameters` | +| `environmentConfigs` | [SchemaSection](#schemasection) | No | - | Schema for the per-environment values supplied on `ProjectReleaseBinding.spec.environmentConfigs` | +| `validations` | [[ValidationRule](#validationrule)] | No | - | CEL rules evaluated during rendering; all must evaluate to true for rendering to proceed | +| `resources` | [[ResourceTemplate](#resourcetemplate)] | Yes | - | Namespace-scoped manifest templates applied to the cell namespace (min: 1). Must include the mandated `v1/Namespace` entry | + +:::note +The rendered output of `resources` must include a `v1/Namespace` object whose `metadata.name` is the literal `${metadata.namespace}` placeholder. The check runs against the rendered output, so an `includeWhen` that suppresses the entry also fails it. A missing namespace surfaces on the binding as `Synced=False` with reason `NamespaceMissing`. +::: + +### SchemaSection + +| Field | Type | Required | Description | +| ----------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------- | +| `openAPIV3Schema` | object | No | Schema in standard OpenAPI V3 / JSON Schema format, with support for defaults, enums, and validation constraints | + +### ValidationRule + +| Field | Type | Required | Description | +| --------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------- | +| `rule` | string | Yes | A CEL expression wrapped in `${...}` that must evaluate to true. Has access to the same context as resource templates | +| `message` | string | Yes | Error message surfaced when the rule evaluates to false (min: 1) | + +### ResourceTemplate + +| Field | Type | Required | Default | Description | +| ------------- | ------ | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `id` | string | Yes | - | Unique identifier for this entry within the type (min: 1) | +| `template` | object | Yes | - | The Kubernetes manifest with `${...}` CEL expressions evaluated at render time | +| `includeWhen` | string | No | - | Boolean CEL expression wrapped in `${...}`; when false, the entry is omitted and any previously applied object is garbage-collected | +| `forEach` | string | No | - | CEL expression wrapped in `${...}` yielding a list to iterate over; generates one object per item | +| `var` | string | No | - | Loop variable name for `forEach` (required when `forEach` is set) | +| `targetPlane` | string | No | `dataplane` | Target plane for the rendered object: `dataplane` or `observabilityplane` | + +Templates have access to `${metadata.*}`, `${parameters.*}`, `${environmentConfigs.*}`, `${environment.*}`, `${dataplane.*}`, and `${gateway.*}`. See the [Authoring ProjectTypes guide](../../../platform-engineer-guide/project-types.md#cel-surface-for-project-templates) for the full CEL surface. + +### Status Fields + +ProjectType does not report status fields. Schema validation failures surface on the referencing Project, and rendering or validation failures surface on the ProjectReleaseBinding's `status.conditions`. + +## Examples + +### Minimal ProjectType + +The smallest valid ProjectType carries only the mandated cell-namespace entry: + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectType +metadata: + name: minimal + namespace: default +spec: + resources: + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${metadata.labels} +``` + +### ProjectType With Quotas and Policies + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectType +metadata: + name: quota-managed + namespace: default +spec: + environmentConfigs: + openAPIV3Schema: + type: object + properties: + cpuQuota: + type: string + default: "4" + memoryQuota: + type: string + default: "8Gi" + validations: + - rule: "${environmentConfigs.cpuQuota.matches('^[0-9]+$')}" + message: "cpuQuota must be an integer string" + resources: + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${metadata.labels} + - id: resource-quota + template: + apiVersion: v1 + kind: ResourceQuota + metadata: + name: project-quota + namespace: ${metadata.namespace} + labels: ${metadata.labels} + spec: + hard: + limits.cpu: ${environmentConfigs.cpuQuota} + limits.memory: ${environmentConfigs.memoryQuota} + - id: default-deny-egress + template: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: default-deny-egress + namespace: ${metadata.namespace} + labels: ${metadata.labels} + spec: + podSelector: {} + policyTypes: [Egress] + egress: [] +``` + +## Usage + +```bash +# List project types in a namespace +kubectl get pt -n default +occ projecttype list + +# Inspect a project type +kubectl describe projecttype quota-managed -n default +occ projecttype get quota-managed + +# Create or update from a manifest +occ apply -f projecttype.yaml +``` + +## Best Practices + +1. Always propagate `${metadata.labels}` onto every rendered object so data-plane objects carry consistent platform labels +2. Set `metadata.namespace: ${metadata.namespace}` on every non-namespace entry so all objects land in the cell namespace +3. Keep developer-facing choices in `parameters` and operational per-environment knobs in `environmentConfigs` +4. Use `validations` for cross-field constraints the schema cannot express +5. Prefer a ClusterProjectType for templates intended to be shared platform-wide + +## Related Resources + +- [ClusterProjectType](./clusterprojecttype.md): cluster-scoped variant +- [Project](../application/project.md): references the type through `spec.type` +- [ProjectRelease](../runtime/projectrelease.md): immutable snapshot that inlines the type spec +- [ProjectReleaseBinding](./projectreleasebinding.md): renders the inlined type per environment +- [Authoring ProjectTypes (PE Guide)](../../../platform-engineer-guide/project-types.md) diff --git a/docs/reference/api/runtime/projectrelease.md b/docs/reference/api/runtime/projectrelease.md new file mode 100644 index 00000000..855c2c39 --- /dev/null +++ b/docs/reference/api/runtime/projectrelease.md @@ -0,0 +1,117 @@ +--- +title: ProjectRelease API Reference +description: Immutable snapshot of a Project and its referenced ProjectType at a point in time +--- + +# ProjectRelease + +A ProjectRelease is an immutable snapshot of a [Project](../application/project.md) and its referenced [ProjectType](../platform/projecttype.md) (or [ClusterProjectType](../platform/clusterprojecttype.md)) at a point in time. It works like a lock file: the type's spec and the project's parameter values are frozen into the release, so later edits to the ProjectType never affect releases that were already cut. + +The ProjectRelease is the unit that [ProjectReleaseBindings](../platform/projectreleasebinding.md) pin to environments. Promoting a project from one environment to the next means pointing the higher environment's binding at the same release; the release itself never changes. + +The Project controller cuts releases automatically: whenever the inlined type snapshot or the parameter values change, a new ProjectRelease named `-` is created and recorded on the Project's `status.latestRelease`. Releases can also be authored externally (GitOps, the OpenChoreo API, or `kubectl`). There is no ProjectRelease controller logic beyond that; the resource is a pure artifact. + +## API Version + +`openchoreo.dev/v1alpha1` + +## Resource Definition + +### Metadata + +ProjectReleases are namespace-scoped resources created in the same namespace as the Project they snapshot. Controller-cut releases are named `-`. + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectRelease +metadata: + name: - + namespace: +``` + +### Spec Fields + +The entire spec is immutable after creation. Edits are rejected by a CEL validation rule on the CRD. + +| Field | Type | Required | Description | +| ------------- | ------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------ | +| `owner` | [ProjectReleaseOwner](#projectreleaseowner) | Yes | Identifies the project this release belongs to | +| `projectType` | [ProjectReleaseProjectType](#projectreleaseprojecttype) | Yes | Frozen snapshot of the referenced (Cluster)ProjectType: kind, name, and the full spec | +| `parameters` | object | No | Snapshot of `Project.spec.parameters` at release time, validated against the type's schema | + +### ProjectReleaseOwner + +| Field | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| `projectName` | string | Yes | Name of the owning project (min: 1) | + +### ProjectReleaseProjectType + +The frozen type snapshot. Both kinds are stored under the same spec shape, since ClusterProjectTypeSpec currently mirrors ProjectTypeSpec. + +| Field | Type | Required | Description | +| ------ | --------------------------------------------------------- | -------- | ----------------------------------------------------- | +| `kind` | string | Yes | `ProjectType` or `ClusterProjectType` | +| `name` | string | Yes | Name of the type the snapshot was taken from (min: 1) | +| `spec` | [ProjectTypeSpec](../platform/projecttype.md#spec-fields) | Yes | The full type spec as it was at snapshot time | + +### Status Fields + +ProjectRelease does not report status fields. Rendering state surfaces on the ProjectReleaseBinding that pins the release. + +## Examples + +### Controller-Cut ProjectRelease + +```yaml +apiVersion: openchoreo.dev/v1alpha1 +kind: ProjectRelease +metadata: + name: online-store-a1b2c3d4 + namespace: default +spec: + owner: + projectName: online-store + projectType: + kind: ClusterProjectType + name: standard-project + spec: + environmentConfigs: + openAPIV3Schema: + type: object + properties: + cpuQuota: + type: string + default: "4" + resources: + - id: cell-namespace + template: + apiVersion: v1 + kind: Namespace + metadata: + name: ${metadata.namespace} + labels: ${metadata.labels} + parameters: + tier: premium +``` + +## Immutability + +ProjectRelease's spec is enforced immutable by a CEL validation rule (`self == oldSelf`). All fields (`owner`, `projectType`, `parameters`) are part of the snapshot guarantee: + +- Spec edits via `kubectl edit` or API PATCH are rejected. +- The Project controller cuts a new ProjectRelease (new hash, new name) when the inlined (Cluster)ProjectType spec or the project's `parameters` change. Previous snapshots are left untouched, so environments pinned to them keep running exactly what they were promoted to. + +## Lifecycle + +1. **Create.** The Project controller computes the hash of the inlined (Cluster)ProjectType spec plus `Project.spec.parameters`. When it drifts from `status.latestRelease.hash`, the controller creates a release named `{project}-{hash}` and updates `Project.status.latestRelease`. Releases can also be authored externally (GitOps, the OpenChoreo API, or `kubectl`); the controller refuses to claim an existing name owned by a different project. +2. **Promote.** A [ProjectReleaseBinding](../platform/projectreleasebinding.md)'s `spec.projectRelease` is advanced to point at this snapshot, through `occ project deploy --to `, a GitOps commit, or a kubectl patch. The binding controller renders the snapshot's `projectType.spec` with the snapshot's `parameters` and the binding's `environmentConfigs`, creates the cell namespace, and applies the rendered manifests to the data plane. +3. **Delete.** When the parent Project is deleted, its cleanup finalizer removes the project's Components, Resources, and ProjectReleaseBindings, tearing down the data-plane namespaces the bindings own. Direct `kubectl delete projectrelease ...` is not blocked by a finalizer but breaks the binding chain if any binding still pins the snapshot. + +## Related Resources + +- [Project](../application/project.md): source of the snapshot; tracks the newest release on `status.latestRelease` +- [ProjectType](../platform/projecttype.md) / [ClusterProjectType](../platform/clusterprojecttype.md): the inlined template +- [ProjectReleaseBinding](../platform/projectreleasebinding.md): pins a release to an environment +- [ComponentRelease](./componentrelease.md): component-side counterpart +- [ResourceRelease](./resourcerelease.md): resource-side counterpart diff --git a/sidebars.ts b/sidebars.ts index f69c180a..86f5462f 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -133,6 +133,11 @@ const sidebars: SidebarsConfig = { id: "platform-engineer-guide/resource-types", label: "Resource Types", }, + { + type: "doc", + id: "platform-engineer-guide/project-types", + label: "Project Types", + }, { type: "category", label: "Workflows & CI", @@ -345,6 +350,7 @@ const sidebars: SidebarsConfig = { items: [ "developer-guide/deploying-applications/overview", "developer-guide/deploying-applications/deploy-and-promote", + "developer-guide/deploying-applications/project-releases", "developer-guide/deploying-applications/environment-overrides", "developer-guide/deploying-applications/logs-and-troubleshooting", ], @@ -562,6 +568,16 @@ const sidebars: SidebarsConfig = { id: "reference/api/platform/clusterresourcetype", label: "ClusterResourceType", }, + { + type: "doc", + id: "reference/api/platform/projecttype", + label: "ProjectType", + }, + { + type: "doc", + id: "reference/api/platform/clusterprojecttype", + label: "ClusterProjectType", + }, { type: "doc", id: "reference/api/platform/workflow", @@ -609,6 +625,11 @@ const sidebars: SidebarsConfig = { id: "reference/api/platform/resourcereleasebinding", label: "ResourceReleaseBinding", }, + { + type: "doc", + id: "reference/api/platform/projectreleasebinding", + label: "ProjectReleaseBinding", + }, ], }, { @@ -687,6 +708,11 @@ const sidebars: SidebarsConfig = { id: "reference/api/runtime/resourcerelease", label: "ResourceRelease", }, + { + type: "doc", + id: "reference/api/runtime/projectrelease", + label: "ProjectRelease", + }, { type: "doc", id: "reference/api/runtime/renderedrelease",