Skip to content
Merged
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
127 changes: 126 additions & 1 deletion extras/dagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,131 @@ dagger call -m github.com/chainloop-dev/chainloop \
mark-canceled --reason "nothing to see here"
```

## 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)

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
Expand Down Expand Up @@ -202,6 +327,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)
152 changes: 146 additions & 6 deletions extras/dagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ type Attestation struct {
// +private
RegistryAuth RegistryAuth
Client *Chainloop

// +private
parentCIContext *ParentCIContext
// +private
githubEventFile *dagger.File
}

// Configuration for a container registry client
Expand All @@ -62,6 +67,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,
Expand All @@ -86,11 +121,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{
Expand Down Expand Up @@ -344,14 +438,60 @@ 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
WithEnvVariable("CHAINLOOP_DAGGER_CLIENT", chainloopVersion).
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)
}
Expand Down Expand Up @@ -390,7 +530,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)
}
Expand Down Expand Up @@ -537,7 +677,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,
Expand Down
11 changes: 11 additions & 0 deletions pkg/attestation/crafter/prmetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading