Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions docs/2.0/docs/pipelines/guides/hooks/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Authentication & Secrets

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

A hook often needs to call a cloud API or reach an external service: to inspect live resources, fetch a secret, or post a notification. The [`authentication`](/2.0/reference/pipelines/configurations-as-code/api#authentication-block) block on an [`after_hook`](/2.0/reference/pipelines/configurations-as-code/api#after_hook-block) gives the hook's `execute` command a cloud identity to do this. When it is present, Pipelines authenticates and makes the resulting credentials available to the hook before running its command. When it is omitted, the hook runs with no cloud credentials.

## Cloud credentials

The `authentication` block authenticates the hook against a cloud provider. It supports AWS, Azure, and GCP through their OIDC blocks (`aws_oidc`, `azure_oidc`, `gcp_oidc`), as well as a `custom` block that runs your own command to obtain credentials. Each provider takes a separate identity for plan and for apply: Pipelines authenticates with the plan identity when the hook runs after a `plan`, and the apply identity when it runs after an `apply`. The two can be the same or different.

Once the hook is authenticated, the provider's CLIs and SDKs work inside it with no further configuration. Configure the block for your provider:

<Tabs groupId="cloud-provider">
<TabItem value="aws" label="AWS" default>

```hcl
repository {
after_hook "inspect_resources" {
commands = ["plan"]
execute = [".gruntwork/hooks/inspect-resources.sh"]

authentication {
aws_oidc {
account_id = "123456789012"
plan_iam_role_arn = "arn:aws:iam::123456789012:role/pipelines-plan"
apply_iam_role_arn = "arn:aws:iam::123456789012:role/pipelines-apply"
}
}
}
}
```

</TabItem>
<TabItem value="azure" label="Azure">

```hcl
repository {
after_hook "inspect_resources" {
commands = ["plan"]
execute = [".gruntwork/hooks/inspect-resources.sh"]

authentication {
azure_oidc {
tenant_id = "a-tenant-id"
subscription_id = "a-subscription-id"
plan_client_id = "plan-client-id"
apply_client_id = "apply-client-id"
}
}
}
}
```

</TabItem>
<TabItem value="gcp" label="GCP">

```hcl
repository {
after_hook "inspect_resources" {
commands = ["plan"]
execute = [".gruntwork/hooks/inspect-resources.sh"]

authentication {
gcp_oidc {
workload_identity_provider_id = "projects/123456789012/locations/global/workloadIdentityPools/pipelines-pool/providers/pipelines-provider"
plan_service_account_email = "pipelines-plan@my-gcp-project.iam.gserviceaccount.com"
apply_service_account_email = "pipelines-apply@my-gcp-project.iam.gserviceaccount.com"
}
}
}
}
```

</TabItem>
<TabItem value="custom" label="Custom">

```hcl
repository {
after_hook "inspect_resources" {
commands = ["plan"]
execute = [".gruntwork/hooks/inspect-resources.sh"]

authentication {
custom {
auth_provider_cmd = "./scripts/auth-provider.sh"
}
}
}
}
```

</TabItem>
</Tabs>

For setting up each provider and the full set of fields, see [Authenticating to the Cloud](/2.0/docs/pipelines/concepts/cloud-auth/index.md) and the [`authentication` block reference](/2.0/reference/pipelines/configurations-as-code/api#authentication-block).

## Secrets

Pipelines does not load secrets into a hook for you. It is up to the hook author to decide how a secret is stored and retrieved. What the `authentication` block provides is the context, a cloud identity, that lets the hook retrieve the secret itself at runtime.

The pattern is the same whatever your provider: store the secret in a secret store, grant the hook's identity permission to read it, and have the hook fetch it at runtime using the credentials the `authentication` block already provides. The secret never appears in your configuration or the hook script.

For a working example using AWS and SSM Parameter Store, see [Example: Slack Deploy Notification](/2.0/docs/pipelines/guides/hooks/slack-deploy-notification). For other ways to manage and supply secrets across Pipelines, see [Managing Secrets in your Pipelines](/2.0/docs/pipelines/guides/managing-secrets).

## Related documentation

- [Writing a Hook](/2.0/docs/pipelines/guides/hooks/writing-a-hook)
- [`authentication` block reference](/2.0/reference/pipelines/configurations-as-code/api#authentication-block)
- [Authenticating to the Cloud](/2.0/docs/pipelines/concepts/cloud-auth/index.md)
95 changes: 95 additions & 0 deletions docs/2.0/docs/pipelines/guides/hooks/configuring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Configuring Hooks

Hooks are configured in your Pipelines HCL configuration.

## After hooks

After hooks run after Pipelines completes a `plan` or `apply`. They are configured with [`after_hook`](/2.0/reference/pipelines/configurations-as-code/api#after_hook-block) blocks nested inside the `repository` block. Each block declares which commands it runs after and the command to run. You can define multiple after hooks, and they run in the order they are defined.

```hcl
repository {
after_hook "hello_world" {
commands = ["plan"]
execute = ["echo", "Hello, World!"]
}
}
```

### Required fields

- **`commands`**: the Pipelines commands this hook runs after. One or both of `plan` and `apply`.
- **`execute`**: the command to run, given as a list of the program followed by its arguments.

The block label (`hello_world` in the example above) is also required and must be unique within the `repository` block.

### Optional fields

- **`name`**: a human-readable display name for the hook.
- **`env`**: environment variables to set for the `execute` command.
- **`run_on_error`**: whether the hook runs when a preceding command or hook failed. Defaults to `false`.
- **`timeout_seconds`**: how long the hook may run before it is terminated. Defaults to `300`.
- **`authentication`**: cloud credentials and secrets for the hook. See [Authentication & Secrets](/2.0/docs/pipelines/guides/hooks/authentication).
Comment on lines +18 to +31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be best to leave out the Required and optional sections here since we link to the full spec below and can have a single source of truth for the documentation.

We should still have the text explaining the label, commands and execute meaning.


See the [`after_hook` block attributes](/2.0/reference/pipelines/configurations-as-code/api#after_hook-block-attributes) reference for full details.

## How hooks execute

### Hooks only run when units are affected

Hooks only run when the Pipelines run affected at least one unit. A unit is affected when the run actually planned or applied it; units excluded from the run do not count.

If a change produces no work, hooks are skipped and the run still succeeds. This covers two cases:

- The change touches no unit, so Pipelines schedules no jobs to run.
- Jobs run, but no units are affected (for example an edit to a file that does not belong to any unit).

In both cases there is nothing for a hook to act on, so no hooks run.

### Command filtering

A run executes a single command, either `plan` or `apply`, and only hooks whose `commands` include that command run. A hook scoped to `apply` does not run on a pull/merge request plan, and a hook scoped to `plan` does not run on an apply.

A destroy is treated as an `apply` for this purpose, so a hook configured with `commands = ["apply"]` also runs after a destroy.

### Isolated working directory

Each hook runs in its own temporary copy of the repository, with that copy as its working directory. This is why an `execute` path like `.gruntwork/hooks/affected-units.sh` resolves relative to the repository root.

Any changes a hook makes to files are not persisted. The copy is discarded once the hook finishes, so edits are never committed, pushed, or seen by the rest of the run. Because each hook gets its own fresh copy, hooks also do not see file changes made by other hooks.

### Exit codes

A hook's exit code is how it tells Pipelines whether it succeeded:

- **Exit `0`** means the hook succeeded. Pipelines reads back its output files (result, summary, and comment).
- **Any non-zero exit** means the hook failed. **A failed hook fails the entire pipeline run**, exactly as a failed `plan` or `apply` does, and Pipelines ignores the hook's output files.

The exit code is not the only thing that can fail the run. When a hook exits `0`, Pipelines reads the result it wrote (`pass`, `warn`, or `deny`) and surfaces it in the comment. A `deny` result fails the pipeline run and blocks the pull/merge request from merging. `warn` is advisory and does not affect the run, and `pass` (or an empty or unrecognized value) has no effect. See [Hooks API](/2.0/reference/pipelines/hooks-api) for the result values.

### Skipping after a failure

By default, a hook is skipped if anything earlier in the run failed. This includes:

- the `plan` or `apply` the hook runs after, or
- an earlier hook in the list that exited non-zero.

A skipped hook does not run, and is reported as skipped on the pull/merge request.

Set `run_on_error = true` to run the hook regardless of an earlier failure. This is useful for hooks that should always run, such as sending a notification whether the run succeeded or failed. A `run_on_error` hook still runs even when a preceding hook failed.

### Timeout and cancellation

Each hook has a `timeout_seconds` limit (default `300`). The limit covers the whole hook, including acquiring any credentials from its [`authentication`](/2.0/docs/pipelines/guides/hooks/authentication) block. A hook that runs longer than its limit is cancelled.

When a hook is cancelled, Pipelines signals the hook's process group to terminate, gives it a brief grace period to exit cleanly, and then forcibly kills it. Because the whole process group is signalled, any child processes the hook started are terminated too.

A cancelled hook counts as a failure: it fails the run and, like any failure, causes later hooks without `run_on_error = true` to be skipped.

### Inputs and outputs

Pipelines passes information to a hook through environment variables, and a hook returns information by writing to files whose paths Pipelines provides. See [Hooks API](/2.0/reference/pipelines/hooks-api) for the full contract.

## Next steps

- [Hooks API](/2.0/reference/pipelines/hooks-api)
- [Writing a Hook](/2.0/docs/pipelines/guides/hooks/writing-a-hook)
34 changes: 34 additions & 0 deletions docs/2.0/docs/pipelines/guides/hooks/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Hooks

:::info

Hooks are an Enterprise-only feature.

:::

Hooks are how you extend a Pipelines run with your own tooling. If you have used Terragrunt's before and after hooks, the model will feel familiar: you declare a hook and Pipelines runs your command at a defined point in the run.

This unblocks the kinds of integrations teams reach for most when running infrastructure changes at scale, such as cost estimation, security scanning, policy enforcement, auditing, and notifications.

Hooks are configured in your Pipelines HCL configuration. Each hook declares whether it runs after `plan` and/or `apply`, and the command to execute.

Pipelines passes each hook context about the run through environment variables (for example the actor, repository, and action). *After hooks* additionally receive the run's OpenTofu/Terraform plan. In turn, a hook can write outputs that Pipelines reflects back in the pull/merge request comment, so its results show up alongside the plan or apply summary. See [Hooks API](/2.0/reference/pipelines/hooks-api) for the full contract.

:::note

Hooks are under active development, and new capabilities will continue to roll out over time. Expect this documentation to expand alongside them.

:::

## In this section

- [Setup & Prerequisites](/2.0/docs/pipelines/guides/hooks/setup) - what you need before configuring a hook.
- [Configuring Hooks](/2.0/docs/pipelines/guides/hooks/configuring) - how to declare a hook and how hooks execute.
- [Hooks API](/2.0/reference/pipelines/hooks-api) - the environment variables and files exchanged with a hook.
- [Writing a Hook](/2.0/docs/pipelines/guides/hooks/writing-a-hook) - a step-by-step guide to authoring your own hook.
- [Authentication & Secrets](/2.0/docs/pipelines/guides/hooks/authentication) - giving a hook cloud credentials and secrets when it runs.
- [Example: Slack Deploy Notification](/2.0/docs/pipelines/guides/hooks/slack-deploy-notification) - a worked example.

## Related documentation

- [`after_hook` block reference](/2.0/reference/pipelines/configurations-as-code/api#after_hook-block) - the full list of configurable fields.
64 changes: 64 additions & 0 deletions docs/2.0/docs/pipelines/guides/hooks/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Setup & Prerequisites

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Before you can configure a hook, your repository needs to meet a couple of prerequisites.

## Enterprise license

:::info

Hooks are an Enterprise-only feature.

:::

## Plan encryption key

When any hooks are configured, the `PIPELINES_PLAN_ENCRYPTION_KEY` secret must be set.

Pipelines adds the OpenTofu/Terraform plan output to the job's artifacts so that after-hooks can read it. Because plan output can contain sensitive information, Pipelines encrypts it before storing it as an artifact, and the `PIPELINES_PLAN_ENCRYPTION_KEY` secret is the key used to do so.

If a hook is declared and this secret is missing, Pipelines fails its preflight checks before running.

### Generating a key

The secret can be any non-empty value. Use a long, randomly generated value rather than a memorable passphrase. For example:

```bash
openssl rand -base64 32
```

Store the generated value somewhere safe (such as a password manager) and treat it like any other sensitive credential. If you rotate the key, plan artifacts encrypted with the previous value can no longer be decrypted.

### Configuring the secret

Make the generated value available to your Pipelines workflows as a secret named `PIPELINES_PLAN_ENCRYPTION_KEY`.

<Tabs groupId="platform">
<TabItem value="github" label="GitHub" default>

Add a repository or organization secret named `PIPELINES_PLAN_ENCRYPTION_KEY` under **Settings > Secrets and variables > Actions**. See [GitHub's documentation on encrypted secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions) for details.

Then pass it through to the Pipelines workflow in your `.github/workflows/pipelines.yml` by adding it to the `secrets` block:

```yml
jobs:
GruntworkPipelines:
uses: gruntwork-io/pipelines-workflows/.github/workflows/pipelines.yml@v4
secrets:
# ... other secrets ...
PIPELINES_PLAN_ENCRYPTION_KEY: ${{ secrets.PIPELINES_PLAN_ENCRYPTION_KEY }}
```

</TabItem>
<TabItem value="gitlab" label="GitLab">

Add a project or group CI/CD variable named `PIPELINES_PLAN_ENCRYPTION_KEY` under **Settings > CI/CD > Variables**. Mark it **Masked** so the value is not exposed in job logs, and leave both **Protect variable** and **Expand variable reference** unchecked. The variable must not be protected so that it is available on the feature branch pipelines where Pipelines runs `plan`. See [GitLab's documentation on CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) for details.

</TabItem>
</Tabs>

## Next steps

- [Configuring Hooks](/2.0/docs/pipelines/guides/hooks/configuring)
Loading