From 802180fbcfc1e6e140a10bdc8817c0706ecccb3c Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Wed, 7 Jan 2026 13:54:30 +0100 Subject: [PATCH 1/3] feat(dagger): Allow to pass GitHub/GitLab env variable for PR detection Signed-off-by: Javier Rodriguez --- extras/dagger/README.md | 126 ++++++++++++++++++- extras/dagger/main.go | 167 +++++++++++++++++++++++++- pkg/attestation/crafter/prmetadata.go | 11 ++ 3 files changed, 297 insertions(+), 7 deletions(-) diff --git a/extras/dagger/README.md b/extras/dagger/README.md index 802c22171..43047873b 100644 --- a/extras/dagger/README.md +++ b/extras/dagger/README.md @@ -166,6 +166,130 @@ dagger call -m github.com/chainloop-dev/chainloop \ mark-canceled --reason "nothing to see here" ``` +## PR/MR Auto-Detection in Parent CI + +When running Chainloop via Dagger inside Github Actions or Gitlab CI, you can enable automatic PR/MR metadata collection by passing the parent CI environment context to the `init` command. This allows Chainloop to automatically detect and include pull request or merge request information in your attestations. + +### Github Actions Example (SDK) + +When using the Dagger SDK in Github Actions, you can pass PR context as individual parameters. The event file would need to be provided as a File parameter from your module's calling context: + +```go +attestation, err := dag.Chainloop().Init( + ctx, + dag.SetSecret("chainloop-token", os.Getenv("CHAINLOOP_TOKEN")), + dagger.ChainloopInitOpts{ + WorkflowName: "build", + ProjectName: "my-project", + Repository: dag.CurrentModule().Source(), + GithubEventName: os.Getenv("GITHUB_EVENT_NAME"), + GithubHeadRef: os.Getenv("GITHUB_HEAD_REF"), + GithubBaseRef: os.Getenv("GITHUB_BASE_REF"), + // GithubEventFile would be passed from caller if available + // GithubEventFile: eventFile, + }, +) +if err != nil { + return err +} +``` + +> **Note**: For full Github PR detection with event file parsing, using the **Dagger CLI** (see below) is recommended as it can directly mount the event file from `$GITHUB_EVENT_PATH`. + +**Required Environment Variables**: +- `GITHUB_EVENT_NAME` - Must be "pull_request" or "pull_request_target" +- `GITHUB_HEAD_REF` - Source branch name +- `GITHUB_BASE_REF` - Target branch name +- `GITHUB_EVENT_PATH` - Path to event JSON file (for CLI usage) + +### Gitlab CI Example (SDK) + +Gitlab MR detection from the Dagger SDK: + +```go +attestation, err := dag.Chainloop().Init( + ctx, + dag.SetSecret("chainloop-token", os.Getenv("CHAINLOOP_TOKEN")), + dagger.ChainloopInitOpts{ + WorkflowName: "build", + ProjectName: "my-project", + Repository: dag.CurrentModule().Source(), + GitlabPipelineSource: os.Getenv("CI_PIPELINE_SOURCE"), + GitlabMRIID: os.Getenv("CI_MERGE_REQUEST_IID"), + GitlabMRTitle: os.Getenv("CI_MERGE_REQUEST_TITLE"), + GitlabMRDescription: os.Getenv("CI_MERGE_REQUEST_DESCRIPTION"), + GitlabMRSourceBranch: os.Getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"), + GitlabMRTargetBranch: os.Getenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME"), + GitlabMRProjectURL: os.Getenv("CI_MERGE_REQUEST_PROJECT_URL"), + GitlabUserLogin: os.Getenv("GITLAB_USER_LOGIN"), + }, +) +if err != nil { + return err +} +``` + +**Required Environment Variables**: +- `CI_PIPELINE_SOURCE` - Must be "merge_request_event" +- `CI_MERGE_REQUEST_IID` - MR number +- `CI_MERGE_REQUEST_TITLE` - MR title +- `CI_MERGE_REQUEST_DESCRIPTION` - MR description +- `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` - Source branch +- `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` - Target branch +- `CI_MERGE_REQUEST_PROJECT_URL` - Project URL +- `GITLAB_USER_LOGIN` - User login + +### Dagger CLI Usage (from Github Actions) + +When calling the Dagger module directly from the Dagger CLI in a Github Actions workflow: + +```sh +dagger call -m github.com/chainloop-dev/chainloop \ + init \ + --token env:CHAINLOOP_TOKEN \ + --workflow-name build \ + --project-name my-project \ + --github-event-file=$GITHUB_EVENT_PATH \ + --github-event-name=$GITHUB_EVENT_NAME \ + --github-head-ref=$GITHUB_HEAD_REF \ + --github-base-ref=$GITHUB_BASE_REF \ + add-raw-evidence \ + --name evidence \ + --value data \ + push \ + --key env:SIGNING_KEY +``` + +> **Note**: All Github PR parameters are passed as individual flags. The event file is automatically mounted by Dagger CLI. + +### Dagger CLI Usage (from Gitlab CI) + +```sh +dagger call -m github.com/chainloop-dev/chainloop \ + init \ + --token env:CHAINLOOP_TOKEN \ + --workflow-name build \ + --project-name my-project \ + --gitlab-pipeline-source=$CI_PIPELINE_SOURCE \ + --gitlab-mriid=$CI_MERGE_REQUEST_IID \ + --gitlab-mrtitle=$CI_MERGE_REQUEST_TITLE \ + --gitlab-mrdescription=$CI_MERGE_REQUEST_DESCRIPTION \ + --gitlab-mrsource-branch=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME \ + --gitlab-mrtarget-branch=$CI_MERGE_REQUEST_TARGET_BRANCH_NAME \ + --gitlab-mrproject-url=$CI_MERGE_REQUEST_PROJECT_URL \ + --gitlab-user-login=$GITLAB_USER_LOGIN \ + add-raw-evidence \ + --name evidence \ + --value data \ + push \ + --key env:SIGNING_KEY +``` + +### Notes + +- All PR/MR parameters are optional - if not provided, attestations work normally without PR metadata +- PR detection will fail gracefully if required env vars are missing or invalid - the attestation will continue without PR metadata + ## CLI operations This module also provides access to the underlying Chainloop CLI by exposing some of its commands @@ -202,6 +326,6 @@ To learn more, please visit the Chainloop project's documentation website, https Chainloop is developed in the open and is constantly improved by our users, contributors and maintainers. Got a question, comment, or idea? Please don't hesitate to reach out via: -- GitHub [Issues](https://github.com/chainloop-dev/chainloop/issues) +- Github [Issues](https://github.com/chainloop-dev/chainloop/issues) - [Slack](https://join.slack.com/t/chainloop-community/shared_invite/zt-2k34dvx3r-u85uGP_KiLC6ic5Wy4aRnQ) - Youtube [Channel](https://www.youtube.com/channel/UCISrWrPyR_AFjIQYmxAyKdg) diff --git a/extras/dagger/main.go b/extras/dagger/main.go index e8fef6c97..8e32202b1 100644 --- a/extras/dagger/main.go +++ b/extras/dagger/main.go @@ -1,3 +1,18 @@ +// +// Copyright 2023-2025 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Chainloop is an open source project that allows you to collect, attest, and distribute pieces of evidence from your Software Supply Chain. package main @@ -36,6 +51,11 @@ type Attestation struct { // +private RegistryAuth RegistryAuth Client *Chainloop + + // +private + parentCIContext *ParentCIContext + // +private + githubEventFile *dagger.File } // Configuration for a container registry client @@ -62,6 +82,36 @@ type InstanceInfo struct { Insecure bool } +// ParentCIContext holds environment variables from a parent CI system (Github Actions, Gitlab CI) +// to enable PR/MR auto-detection when running Chainloop via Dagger inside those CI systems +type ParentCIContext struct { + // Github Actions PR context + // Event name (e.g., "pull_request", "pull_request_target") + GithubEventName string + // Source branch name + GithubHeadRef string + // Target branch name + GithubBaseRef string + + // Gitlab CI MR context + // Pipeline source (should be "merge_request_event" for MRs) + GitlabPipelineSource string + // Merge request internal ID + GitlabMRIID string + // Merge request title + GitlabMRTitle string + // Merge request description + GitlabMRDescription string + // Source branch name + GitlabMRSourceBranch string + // Target branch name + GitlabMRTargetBranch string + // Project URL + GitlabMRProjectURL string + // User login + GitlabUserLogin string +} + // Initialize a new attestation func (m *Chainloop) Init( ctx context.Context, @@ -86,11 +136,70 @@ func (m *Chainloop) Init( // mark the version as release // +optional release bool, + // Github event file for PR detection (when running in Github Actions) + // +optional + githubEventFile *dagger.File, + // Github event name (e.g., "pull_request", "pull_request_target") + // +optional + githubEventName string, + // Github source branch name + // +optional + githubHeadRef string, + // Github target branch name + // +optional + githubBaseRef string, + // Gitlab pipeline source (should be "merge_request_event" for MRs) + // +optional + gitlabPipelineSource string, + // Gitlab merge request internal ID + // +optional + gitlabMRIID string, + // Gitlab merge request title + // +optional + gitlabMRTitle string, + // Gitlab merge request description + // +optional + gitlabMRDescription string, + // Gitlab source branch name + // +optional + gitlabMRSourceBranch string, + // Gitlab target branch name + // +optional + gitlabMRTargetBranch string, + // Gitlab project URL + // +optional + gitlabMRProjectURL string, + // Gitlab user login + // +optional + gitlabUserLogin string, ) (*Attestation, error) { + // Construct ParentCIContext from individual parameters + var parentCIContext *ParentCIContext + if githubEventName != "" || githubHeadRef != "" || githubBaseRef != "" || + gitlabPipelineSource != "" || gitlabMRIID != "" || gitlabMRTitle != "" || + gitlabMRDescription != "" || gitlabMRSourceBranch != "" || gitlabMRTargetBranch != "" || + gitlabMRProjectURL != "" || gitlabUserLogin != "" { + parentCIContext = &ParentCIContext{ + GithubEventName: githubEventName, + GithubHeadRef: githubHeadRef, + GithubBaseRef: githubBaseRef, + GitlabPipelineSource: gitlabPipelineSource, + GitlabMRIID: gitlabMRIID, + GitlabMRTitle: gitlabMRTitle, + GitlabMRDescription: gitlabMRDescription, + GitlabMRSourceBranch: gitlabMRSourceBranch, + GitlabMRTargetBranch: gitlabMRTargetBranch, + GitlabMRProjectURL: gitlabMRProjectURL, + GitlabUserLogin: gitlabUserLogin, + } + } + att := &Attestation{ - Token: token, - repository: repository, - Client: m, + Token: token, + repository: repository, + Client: m, + parentCIContext: parentCIContext, + githubEventFile: githubEventFile, } // Append the contract revision to the args if provided args := []string{ @@ -344,7 +453,7 @@ func (att *Attestation) Debug() *dagger.Container { return att.Container(0).Terminal() } -func cliContainer(ttl int, token *dagger.Secret, instance InstanceInfo) *dagger.Container { +func cliContainer(ttl int, token *dagger.Secret, instance InstanceInfo, parentCI *ParentCIContext, githubEventFile *dagger.File) *dagger.Container { ctr := dag.Container(). From(fmt.Sprintf("ghcr.io/chainloop-dev/chainloop/cli:%s", chainloopVersion)). WithEntrypoint([]string{"/chainloop"}). // Be explicit to prepare for possible API change @@ -352,6 +461,52 @@ func cliContainer(ttl int, token *dagger.Secret, instance InstanceInfo) *dagger. WithUser(""). // Our images come with pre-defined user set, so we need to reset it WithEnvVariable("DAGGER_CACHE_KEY", time.Now().Truncate(time.Duration(ttl)*time.Second).String()) // Cache TTL + // Inject parent CI context if provided + if parentCI != nil { + // Github Actions context + if parentCI.GithubEventName != "" { + ctr = ctr.WithEnvVariable("GITHUB_EVENT_NAME", parentCI.GithubEventName) + } + if parentCI.GithubHeadRef != "" { + ctr = ctr.WithEnvVariable("GITHUB_HEAD_REF", parentCI.GithubHeadRef) + } + if parentCI.GithubBaseRef != "" { + ctr = ctr.WithEnvVariable("GITHUB_BASE_REF", parentCI.GithubBaseRef) + } + + // Handle Github event file (passed as separate parameter for CLI convenience) + if githubEventFile != nil { + ctr = ctr.WithFile("/tmp/github_event.json", githubEventFile). + WithEnvVariable("GITHUB_EVENT_PATH", "/tmp/github_event.json") + } + + // Gitlab CI context + if parentCI.GitlabPipelineSource != "" { + ctr = ctr.WithEnvVariable("CI_PIPELINE_SOURCE", parentCI.GitlabPipelineSource) + } + if parentCI.GitlabMRIID != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_IID", parentCI.GitlabMRIID) + } + if parentCI.GitlabMRTitle != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_TITLE", parentCI.GitlabMRTitle) + } + if parentCI.GitlabMRDescription != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_DESCRIPTION", parentCI.GitlabMRDescription) + } + if parentCI.GitlabMRSourceBranch != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", parentCI.GitlabMRSourceBranch) + } + if parentCI.GitlabMRTargetBranch != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_TARGET_BRANCH_NAME", parentCI.GitlabMRTargetBranch) + } + if parentCI.GitlabMRProjectURL != "" { + ctr = ctr.WithEnvVariable("CI_MERGE_REQUEST_PROJECT_URL", parentCI.GitlabMRProjectURL) + } + if parentCI.GitlabUserLogin != "" { + ctr = ctr.WithEnvVariable("GITLAB_USER_LOGIN", parentCI.GitlabUserLogin) + } + } + if token != nil { ctr = ctr.WithSecretVariable("CHAINLOOP_TOKEN", token) } @@ -390,7 +545,7 @@ func (att *Attestation) Container( // +default=0 ttl int, ) *dagger.Container { - ctr := cliContainer(ttl, att.Token, att.Client.Instance) + ctr := cliContainer(ttl, att.Token, att.Client.Instance, att.parentCIContext, att.githubEventFile) if att.repository != nil { ctr = ctr.WithDirectory(".", att.repository) } @@ -537,7 +692,7 @@ func (m *Chainloop) WorkflowCreate( // +optional skipIfExists bool, ) (string, error) { - return cliContainer(0, token, m.Instance). + return cliContainer(0, token, m.Instance, nil, nil). WithExec([]string{ "workflow", "create", "--name", name, diff --git a/pkg/attestation/crafter/prmetadata.go b/pkg/attestation/crafter/prmetadata.go index 9a5f9651a..d8766723e 100644 --- a/pkg/attestation/crafter/prmetadata.go +++ b/pkg/attestation/crafter/prmetadata.go @@ -56,6 +56,17 @@ func DetectPRContext(runner SupportedRunner) (bool, *PRMetadata, error) { return extractGitHubPRMetadata(envVars) case schemaapi.CraftingSchema_Runner_GITLAB_PIPELINE: return extractGitLabMRMetadata(envVars) + case schemaapi.CraftingSchema_Runner_DAGGER_PIPELINE: + // When running in Dagger, check for parent CI context passed through as env vars + // Try Github first + if envVars["GITHUB_EVENT_NAME"] != "" { + return extractGitHubPRMetadata(envVars) + } + // Then try Gitlab + if envVars["CI_PIPELINE_SOURCE"] != "" { + return extractGitLabMRMetadata(envVars) + } + return false, nil, nil default: return false, nil, nil } From 50ec80e42e9ab79c56fa7b7a6d89b64c5fcb5c72 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Thu, 8 Jan 2026 08:39:01 +0100 Subject: [PATCH 2/3] tackle feedback Signed-off-by: Javier Rodriguez --- extras/dagger/README.md | 22 +++++++++++----------- extras/dagger/main.go | 15 --------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/extras/dagger/README.md b/extras/dagger/README.md index 43047873b..3ab58ce05 100644 --- a/extras/dagger/README.md +++ b/extras/dagger/README.md @@ -168,11 +168,11 @@ dagger call -m github.com/chainloop-dev/chainloop \ ## PR/MR Auto-Detection in Parent CI -When running Chainloop via Dagger inside Github Actions or Gitlab CI, you can enable automatic PR/MR metadata collection by passing the parent CI environment context to the `init` command. This allows Chainloop to automatically detect and include pull request or merge request information in your attestations. +When running Chainloop via Dagger inside GitHub Actions or GitLab CI, you can enable automatic PR/MR metadata collection by passing the parent CI environment context to the `init` command. This allows Chainloop to automatically detect and include pull request or merge request information in your attestations. -### Github Actions Example (SDK) +### GitHub Actions Example (SDK) -When using the Dagger SDK in Github Actions, you can pass PR context as individual parameters. The event file would need to be provided as a File parameter from your module's calling context: +When using the Dagger SDK in GitHub Actions, you can pass PR context as individual parameters. The event file would need to be provided as a File parameter from your module's calling context: ```go attestation, err := dag.Chainloop().Init( @@ -186,7 +186,7 @@ attestation, err := dag.Chainloop().Init( GithubHeadRef: os.Getenv("GITHUB_HEAD_REF"), GithubBaseRef: os.Getenv("GITHUB_BASE_REF"), // GithubEventFile would be passed from caller if available - // GithubEventFile: eventFile, + GithubEventFile: eventFile, }, ) if err != nil { @@ -194,7 +194,7 @@ if err != nil { } ``` -> **Note**: For full Github PR detection with event file parsing, using the **Dagger CLI** (see below) is recommended as it can directly mount the event file from `$GITHUB_EVENT_PATH`. +> **Note**: For full GitHub PR detection with event file parsing, using the **Dagger CLI** (see below) is recommended as it can directly mount the event file from `$GITHUB_EVENT_PATH`. **Required Environment Variables**: - `GITHUB_EVENT_NAME` - Must be "pull_request" or "pull_request_target" @@ -202,9 +202,9 @@ if err != nil { - `GITHUB_BASE_REF` - Target branch name - `GITHUB_EVENT_PATH` - Path to event JSON file (for CLI usage) -### Gitlab CI Example (SDK) +### GitLab CI Example (SDK) -Gitlab MR detection from the Dagger SDK: +GitLab MR detection from the Dagger SDK: ```go attestation, err := dag.Chainloop().Init( @@ -239,9 +239,9 @@ if err != nil { - `CI_MERGE_REQUEST_PROJECT_URL` - Project URL - `GITLAB_USER_LOGIN` - User login -### Dagger CLI Usage (from Github Actions) +### Dagger CLI Usage (from GitHub Actions) -When calling the Dagger module directly from the Dagger CLI in a Github Actions workflow: +When calling the Dagger module directly from the Dagger CLI in a GitHub Actions workflow: ```sh dagger call -m github.com/chainloop-dev/chainloop \ @@ -260,9 +260,9 @@ dagger call -m github.com/chainloop-dev/chainloop \ --key env:SIGNING_KEY ``` -> **Note**: All Github PR parameters are passed as individual flags. The event file is automatically mounted by Dagger CLI. +> **Note**: All GitHub PR parameters are passed as individual flags. The event file is automatically mounted by Dagger CLI. -### Dagger CLI Usage (from Gitlab CI) +### Dagger CLI Usage (from GitLab CI) ```sh dagger call -m github.com/chainloop-dev/chainloop \ diff --git a/extras/dagger/main.go b/extras/dagger/main.go index 8e32202b1..f9591b4d6 100644 --- a/extras/dagger/main.go +++ b/extras/dagger/main.go @@ -1,18 +1,3 @@ -// -// Copyright 2023-2025 The Chainloop Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // Chainloop is an open source project that allows you to collect, attest, and distribute pieces of evidence from your Software Supply Chain. package main From b177ed2e56ccd39951f86e6da09106467a105104 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Thu, 8 Jan 2026 10:14:54 +0100 Subject: [PATCH 3/3] update documentation Signed-off-by: Javier Rodriguez --- extras/dagger/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extras/dagger/README.md b/extras/dagger/README.md index 3ab58ce05..678d0f02f 100644 --- a/extras/dagger/README.md +++ b/extras/dagger/README.md @@ -166,9 +166,10 @@ dagger call -m github.com/chainloop-dev/chainloop \ mark-canceled --reason "nothing to see here" ``` -## PR/MR Auto-Detection in Parent CI +## Attestation in a GitHub/GitLab PR/MR When running Chainloop via Dagger inside GitHub Actions or GitLab CI, you can enable automatic PR/MR metadata collection by passing the parent CI environment context to the `init` command. This allows Chainloop to automatically detect and include pull request or merge request information in your attestations. +More information about the feature can be found in the [official documentation](https://docs.chainloop.dev/guides/pr-policies-control-gate) where we give a broader context on how Chainloop can help to build Control Gates in your GitHub and GitLab repositories. ### GitHub Actions Example (SDK)