diff --git a/generated/routes.json b/generated/routes.json index ff67848a..5da0938f 100644 --- a/generated/routes.json +++ b/generated/routes.json @@ -85,7 +85,7 @@ }, "/getting-started/advanced-config/sandboxing": { "relPath": "/getting-started/advanced-config/sandboxing.md", - "lastmod": "2026-01-26T19:35:11.000Z" + "lastmod": "2026-02-19T20:33:28.000Z" }, "/getting-started/advanced-config/network-configuration": { "relPath": "/getting-started/advanced-config/network-configuration.md", @@ -97,31 +97,31 @@ }, "/api-reference": { "relPath": "/api-reference/index.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-03T22:03:47.000Z" }, "/api-reference/kubernetes": { "relPath": "/api-reference/kubernetes/index.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-03T22:03:47.000Z" }, "/api-reference/kubernetes/management-api-reference": { "relPath": "/api-reference/kubernetes/management-api-reference.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-20T17:28:22.000Z" }, "/api-reference/kubernetes/agent-api-reference": { "relPath": "/api-reference/kubernetes/agent-api-reference.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-16T17:54:34.000Z" }, "/api-reference/graphql": { "relPath": "/api-reference/graphql.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-03T22:03:47.000Z" }, "/api-reference/rest": { "relPath": "/api-reference/rest.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-11T23:42:02.000Z" }, "/api-reference/terraform": { "relPath": "/api-reference/terraform.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-03T22:03:47.000Z" }, "/plural-features": { "relPath": "/plural-features/index.md", @@ -149,11 +149,11 @@ }, "/plural-features/continuous-deployment/service-templating": { "relPath": "/plural-features/continuous-deployment/service-templating/index.md", - "lastmod": "2026-02-03T20:13:57.978Z" + "lastmod": "2026-02-23T00:01:21.673Z" }, "/plural-features/continuous-deployment/service-templating/supporting-liquid-filters": { "relPath": "/plural-features/continuous-deployment/service-templating/supporting-liquid-filters.md", - "lastmod": "2026-02-03T20:13:57.997Z" + "lastmod": "2026-02-23T00:01:21.700Z" }, "/plural-features/continuous-deployment/lua": { "relPath": "/plural-features/continuous-deployment/lua.md", @@ -289,7 +289,7 @@ }, "/plural-features/flows/preview-environments": { "relPath": "/plural-features/flows/preview-environments.md", - "lastmod": "2025-05-14T21:43:40.000Z" + "lastmod": "2026-02-22T23:55:41.000Z" }, "/plural-features/flows/mcp": { "relPath": "/plural-features/flows/mcp.md", @@ -339,6 +339,22 @@ "relPath": "/plural-features/pr-automation/crds.md", "lastmod": "2025-06-05T13:01:10.000Z" }, + "/plural-features/pr-automation/governance": { + "relPath": "/plural-features/pr-automation/governance/index.md", + "lastmod": "2026-02-22T23:55:41.000Z" + }, + "/plural-features/pr-automation/governance/servicenow": { + "relPath": "/plural-features/pr-automation/governance/servicenow.md", + "lastmod": "2026-02-22T23:55:41.000Z" + }, + "/plural-features/pr-automation/governance/webhook": { + "relPath": "/plural-features/pr-automation/governance/webhook.md", + "lastmod": "2026-02-22T23:55:41.000Z" + }, + "/plural-features/pr-automation/description-patterns": { + "relPath": "/plural-features/pr-automation/description-patterns.md", + "lastmod": "2026-02-22T23:55:41.000Z" + }, "/plural-features/pr-automation/testing": { "relPath": "/plural-features/pr-automation/testing.md", "lastmod": "2025-03-12T14:59:41.000Z" @@ -461,7 +477,7 @@ }, "/deployments/sandboxing": { "relPath": "/getting-started/advanced-config/sandboxing.md", - "lastmod": "2026-01-26T19:35:11.000Z" + "lastmod": "2026-02-19T20:33:28.000Z" }, "/deployments/network-configuration": { "relPath": "/getting-started/advanced-config/network-configuration.md", @@ -597,10 +613,10 @@ }, "/management-api-reference": { "relPath": "/api-reference/kubernetes/management-api-reference.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-20T17:28:22.000Z" }, "/agent-api-reference": { "relPath": "/api-reference/kubernetes/agent-api-reference.md", - "lastmod": "2026-02-03T20:04:53.000Z" + "lastmod": "2026-02-16T17:54:34.000Z" } } \ No newline at end of file diff --git a/pages/plural-features/flows/preview-environments.md b/pages/plural-features/flows/preview-environments.md index 42017809..437f59bb 100644 --- a/pages/plural-features/flows/preview-environments.md +++ b/pages/plural-features/flows/preview-environments.md @@ -46,6 +46,10 @@ spec: template: namespace: "flow-test-pr-{{ pr.number }}" name: "flow-test-pr-{{ pr.number }}" + + syncConfig: + deleteNamespace: true # ensure the temp namespace is deleted on cleanup + helm: values: image: diff --git a/pages/plural-features/pr-automation/description-patterns.md b/pages/plural-features/pr-automation/description-patterns.md new file mode 100644 index 00000000..ab0cf18f --- /dev/null +++ b/pages/plural-features/pr-automation/description-patterns.md @@ -0,0 +1,91 @@ +--- +title: Description Patterns +description: Auto-Correlate Pull Requests with Resources in Plural +--- + +Plural Supports a number of ways to correlate your Pull Request with resources in Plural, these can serve a variety of purposes: + +1. Kick a Plural Service, to minimize GitOps lag on merge. +2. Auto-generate Plural Stack plans for terraform changes, improving the pull request review process. +3. Deferring merges to off-hours to implement service windows. +4. Tie PRs to governance controllers +5. Preview Environment Creation + +{% callout severity="info" header="Ensure Webhook Present!" %} +In all of these cases, it's assumed you have an SCM webhook configured to provide Plural with an event stream for pull request/merge requests from you SCM provider. We support all major SCM providers, and they're easy to set up at Self Service -> SCM Management. +{% /callout %} + +We'll list these all out one-by-one + +## PR <> Service Correlation + +To tie a pull request to a service, simply add a tag like this to the pull requests description body: + +``` +Plural Service: {cluster-handle}/{service-name} +``` + +to your pull request body. Once there, you'll be able to see the pull request in the service's details view in Plural, and once merged, Plural will automatically resync that service with its source of truth in git, allowing for responsive deployment of the new configuration. + +If this isn't implemented, the ordinary GitOps sync window is something like ~2m with some jitter + +## PR <> Plural Stack Correlation + +Plural Stacks can be tied to pull requests easily with: + +``` +Plural Stack: {stack-name} +``` + +This serves two main purposes: + +1. Like with services, pr merges will auto-kick the stack, ensuring they sync the new changes with git quickly. Ordinarily these follow a gitops sync window like with services, tuned to ~5m +- jitter by default. +2. Allows for terraform plans to be generated and posted to pull requests as comments, this makes it much easier to safely review terraform code based on the changes it implies against real infrastructure. + +The plan will include: + +1. The full terraform plan result against the current state of the stack whenever a commit is made to the PR +2. If Plural AI is enabled, an ai explanation of the plan, and any risks it might pose, which is a nice additional review in case engineers are unfamiliar with the APIs involved. + +## Plural merge cron + +Plural has the ability to auto-merge a PR based on a crontab, assuming the following: + +1. The PR is already approved +2. A `default` SCM connection has been registered within Plural to use. + +It is activated with the following PR body tag: + +``` +Plural merge cron: */5 1,2 * * * +``` + +This will trigger Plural to attempt auto-merge from 1am-2am every 5 minutes. To ensure the PR is defer-merged, approve it before that window and Plural will merge it on your behalf. + +## PR <> Plural Governance Correlation + +To understand how PR governance works, I recommend reading the [docs themselves](/plural-features/pr-automation/governance). That said, tying a PR to a governance controller is simple: + +``` +Plural governance: {governance-name} +``` + +From there, the governance controller will go through its workflow of: + +1. create a change on PR open +2. confirm the change is approved while the PR is open, and if so, approve the PR +3. If the PR is closed or merged, close out the change with the appropriate status. + +## PR -> Preview Environment Creation/Deletion + +Plural Flows supports a full PR-based preview environment controller. You can learn more about it in its [dedicated docs page](/plural-features/flows/preview-environment), but specifying one is simple: + +``` +Plural Flow: {flow-name} +Plural Preview: {preview-environment-template-name} +``` + +This will trigger preview environment creation based on the given template, which is unique to a Flow. In particular this will: + +1. Clone an existing service on PR creation, and auto-updates that service whenever the PR receives a push event. +2. Delete the service when the PR is deleted. \ No newline at end of file diff --git a/pages/plural-features/pr-automation/governance/index.md b/pages/plural-features/pr-automation/governance/index.md new file mode 100644 index 00000000..caa96f65 --- /dev/null +++ b/pages/plural-features/pr-automation/governance/index.md @@ -0,0 +1,53 @@ +--- +title: PR Governance Controller +description: Unify Change Management Processes within a Git-standard Pull Request Model +--- + +# Motivation + +Many enterprises require robust governance for all change management. This is oftentimes done within blessed systems-of-record, with ServiceNow being the most frequently common. Plural allows for you to synchronize these processes with the GitOps-friendly pull request workflow offered by your SCM provider, using two main strategies: + +1. ServiceNow - dedicated controller implementation to synchronize pull request and ServiceNow change request approvals and status +2. Webhook - custom webhook for any nonstandard systems you might want to implement. + +In all cases the flow has three main phases: + +1. Open - a pull request is opened and a callback is initiated to the external system to create the change record +2. Confirm - the change record is polled on a jittered interval to determine whether the change record has been approved +3. Approval - Plural will approve the Pull Request itself, using a SCM credential you specify +4. Close - The change record is closed once the Pull Request is merged or closed + +## Managing PR Approval + +This flow guarantees pull requests cannot be merged unless dictated by the external system if and only if the SCM credential provided is made a required approver. This can be done in a few different ways: + +1. Repo level configuration. Many repos have global methods to require approvals from specific users and groups. +2. CODEOWNERS - this is a standard convention within Git providers, which delegates specific file paths within a repo to require approvals within users or groups. + +We recomend CODEOWNERS as it's more flexible. + +## Tying a Pull Request to PR Governance + +Most governance policies will be instantiated via CRD, like so: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrGovernance +metadata: + name: example +spec: + type: WEBHOOK + connectionRef: + name: governance # reference to the ScmConnection that is used for Pr approval + configuration: + webhook: + url: https://my.governance.webhook +``` + +To allow the controller to work on a given pr, you simply need to add the following text to its PR description: + +``` +Plural governance: example # replace {example} with whatever name you dedicate the governance controller to +``` + +As long as you have a webhook watching that repo (can be created in the SCM management panel in Plural), we'll dedicate and bind the pr to the controller named `example`. \ No newline at end of file diff --git a/pages/plural-features/pr-automation/governance/servicenow.md b/pages/plural-features/pr-automation/governance/servicenow.md new file mode 100644 index 00000000..56a11fa6 --- /dev/null +++ b/pages/plural-features/pr-automation/governance/servicenow.md @@ -0,0 +1,62 @@ +--- +title: ServiceNow Governance +description: Dedicated Pr <-> Change Request Sync intos ServiceNow +--- + +The ServiceNow governance controller allows a pull request to be synchronized with a ServiceNow change request, so enterprises can implement GitOps flexibly without sacrificing their auditing and governance posture built around the ServiceNow ecosystem. Before implementing, it's worth just looking through the [docs on PR governance](/plural-features/pr-automation/governance) briefly to familiarize yourself with the model. + +{% callout severity="info" header="Ensure Webhook Present!" %} +For this to work, it's assumed you have an SCM webhook configured to provide Plural with an event stream for pull request/merge requests from you SCM provider. We support all major SCM providers, and they're easy to set up at Self Service -> SCM Management. Most SCM providers also have ways to limit the scope of webhook events to individual repository sets in case it cannot be implemented organization-wide. +{% /callout %} + +# Defining a PrGovernance CRD + +The Governance CRD utilizing SericeNow is relatively straightforward, here's an example: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrGovernance +metadata: + name: snow +spec: + type: SERVICE_NOW + connectionRef: + name: governance # reference to the ScmConnection that is used for Pr approval + configuration: + serviceNow: + url: https://instance.service-now.com + username: my-user + passwordSecretKeyRef: + name: snow-creds + key: password + secretNamespace: infra # wherever the `snow-creds` secret lives + + # supports the ITIL4 change models, eg Standard, Normal, Emergency + changeModel: Standard + + # any specific attributes you want to include in the change request, we'll auto-infer the required fields (description, short description, backout plan, test plan, implementation plan from the pull request itself if not provided) + attributes: + description: some description +``` + +From there, to tie it to a pull request, you'll need: + +1. To ensure there's a SCM webhook for the repository pointing to Plural (this can be created in Self Service -> SCM Management in you Plural Console instance) +2. Add the `Plural governance: snow` tag to your PR description so that we'll identify it as requiring governance. + +# ServiceNow Controller Implementation + +The ServiceNow controller will do the following once it is tied to a PR: + +1. Create a new ServiceNow change request using the REST API, filling in any blank fields by inspecting the PR and generating them with AI if not provided (this is overrideable and meant to minimize required, brittle implementation). +2. Wait for the change to be moved to the 'Scheduled' (or later) state in ServiceNow, and approve the pull request, and move the change to `Implement` state from there. +3. Once the PR is merged, the change is moved to `Close` state, and marked successful with reason that the pull request is merged. +4. If the PR is ever closed, the change request is moved to `Cancelled` state. + +# Requiring ServiceNow Approvals before PR Approval + +This is acheived in the following way: + +1. Mark the `changeModel` as Normal, this requires the ServiceNow change go through CAB approval before being moved to `Scheduled` state. +2. Ensure the governance SCM connection is a required approver on the PR. This will block merges of the PR until the governance controller sees the ServiceNow approval. + diff --git a/pages/plural-features/pr-automation/governance/webhook.md b/pages/plural-features/pr-automation/governance/webhook.md new file mode 100644 index 00000000..68ab8825 --- /dev/null +++ b/pages/plural-features/pr-automation/governance/webhook.md @@ -0,0 +1,74 @@ +--- +title: Custom Webhook Governance +description: Implementing Webhook Based Custom Governance Controllers +--- + +Some enterprise require bespoke governance, either because they are not standardized on ServiceNow or because they leverage it in a unique, tailor-built way. Plural allows you to bridge that gap with a simple webhook based protocol. Before implementing, it's worth just looking through the [docs on PR governance](/plural-features/pr-automation/governance) briefly to familiarize yourself with the model. + +{% callout severity="info" header="Ensure Webhook Present!" %} +For this to work, it's assumed you have an SCM webhook configured to provide Plural with an event stream for pull request/merge requests from you SCM provider. We support all major SCM providers, and they're easy to set up at Self Service -> SCM Management. Most SCM providers also have ways to limit the scope of webhook events to individual repository sets in case it cannot be implemented organization-wide. +{% /callout %} + +# Defining a PrGovernance CRD + +The Governance CRD utilizing SericeNow is relatively straightforward, here's an example: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrGovernance +metadata: + name: webhook +spec: + type: WEBHOOK + connectionRef: + name: governance # reference to the ScmConnection that is used for Pr approval + configuration: + webhook: + url: https://governance-webhook.acme.com +``` + + +{% callout severity="info" %} +At the moment, this is only feasible on self-hosted instances of Plural +{% /callout %} + +From there, to tie it to a pull request, you'll need: + +1. To ensure there's a SCM webhook for the repository pointing to Plural (this can be created in Self Service -> SCM Management in you Plural Console instance) +2. Add the `Plural governance: webhook` tag to your PR description so that we'll identify it as requiring governance. + +# Webhook Controller Implementation + +The webhook expects three REST-like APIs to be present: + +* POST /v1/open - called when a PR is created +* POST /v1/confirm - called when a PR is open, and will allow auto-approval if it returns 200 +* POST /v1/close - called once a PR is merged or closed, and allows your implementation to cleanup any needed resources + +Throughout the request flow, we will persist the results from prior calls as state, and provide a JSON-encoded request body in the following format: + +```typescript +{ + "state": any | null, + "pr": { + "id": string, + "title": string, + "body": string, + "ref": string, + "url": string, + "status": "open" | "closed" | "merged", + "labels": string[] + } +} +``` + +The webhook controller will do the following once it is tied to a PR: + +1. On PR open, call `POST /v1/open` +2. While the PR is open, periodically call `POST /v1/confirm` with the body from the prior call to `/v1/open` provided in the `state` field above. +3. If `/v1/confirm` returns 200, the pull request is approved. +4. Once the pull request is merged or closed, a call to `POST /v1/close` is made. + +{% callout severity="info" %} +For this to gate pull request merges appropriately, you'll need to ensure the SCM credential given to the governance controller is a required approver of the repository or at least a CODEOWNER of the files that are being changed +{% /callout %} \ No newline at end of file diff --git a/src/routing/docs-structure.ts b/src/routing/docs-structure.ts index 4011ad3d..71a39839 100644 --- a/src/routing/docs-structure.ts +++ b/src/routing/docs-structure.ts @@ -238,6 +238,15 @@ export const docsStructure: DocSection[] = [ title: 'Pull request automation', sections: [ { path: 'crds', title: 'PR automation custom resources' }, + { + path: 'governance', + title: 'PR Governance Controller', + sections: [ + { path: 'servicenow', title: 'ServiceNow Governance' }, + { path: 'webhook', title: 'Custom Webhook Governance' }, + ], + }, + { path: 'description-patterns', title: 'PR Description Patterns' }, { path: 'testing', title: 'PR automation testing' }, { path: 'lua', title: 'Lua-based Pre-Processing' }, { path: 'pipelines', title: 'PR automation pipelines' },