Files
pmg/internal/audit/cloud_env_resolver_github.go
T
6087bc922f feat: populate CI invocation context on cloud events (#304)
* feat: add CloudSinkEnvResolver interface with default implementation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add GitHub Actions environment resolver for cloud sink

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: populate invocation context with CI environment on cloud events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address lint errors in cloud sink tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: use getter-based CloudSinkCIResolver with nil-when-no-CI

Rename to CloudSinkCIResolver with focused CI concern. Factory returns
nil when no CI is detected, removing the need for IsCI() and a default
resolver. Leaves room for a separate agent resolver in the future.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add CI metadata support using updated API SDK

Update SDK to include SetMetadata on EndpointCIContext. Add Metadata()
to CloudSinkCIResolver interface and GitHub Actions implementation
(workflow, job, run_attempt, server_url). Wire metadata into
buildInvocationContext.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address review comments on CI resolver

- Inject CloudSinkCIResolver as dependency into newCloudSink for testability
- Check both GITHUB_ACTIONS and GITHUB_RUN_ID for GHA environment detection
- Make factory and constructor package-private (newCloudSinkCIResolver,
  newGithubActionsCIResolver)
- Attach invocation context only to session complete events, not every event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fail fast on os.Getwd error instead of swallowing it

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-28 19:12:12 +05:30

65 lines
1.7 KiB
Go

package audit
import (
"os"
"regexp"
controltowerv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/controltower/v1"
)
var prRefPattern = regexp.MustCompile(`^refs/pull/(\d+)/merge$`)
type githubActionsEnvResolver struct{}
func newGithubActionsCIResolver() CloudSinkCIResolver {
return &githubActionsEnvResolver{}
}
func (r *githubActionsEnvResolver) Provider() controltowerv1.EndpointCIProvider {
return controltowerv1.EndpointCIProvider_ENDPOINT_CI_PROVIDER_GITHUB_ACTIONS
}
func (r *githubActionsEnvResolver) RunId() string { return os.Getenv("GITHUB_RUN_ID") }
func (r *githubActionsEnvResolver) Repository() string { return os.Getenv("GITHUB_REPOSITORY") }
func (r *githubActionsEnvResolver) CommitSha() string { return os.Getenv("GITHUB_SHA") }
func (r *githubActionsEnvResolver) Actor() string { return os.Getenv("GITHUB_ACTOR") }
func (r *githubActionsEnvResolver) Branch() string {
if headRef := os.Getenv("GITHUB_HEAD_REF"); headRef != "" {
return headRef
}
return os.Getenv("GITHUB_REF_NAME")
}
func (r *githubActionsEnvResolver) PrNumber() string {
matches := prRefPattern.FindStringSubmatch(os.Getenv("GITHUB_REF"))
if len(matches) == 2 {
return matches[1]
}
return ""
}
func (r *githubActionsEnvResolver) Metadata() map[string]string {
entries := []struct {
key string
envVar string
}{
{"workflow", "GITHUB_WORKFLOW"},
{"job", "GITHUB_JOB"},
{"run_attempt", "GITHUB_RUN_ATTEMPT"},
{"server_url", "GITHUB_SERVER_URL"},
}
metadata := make(map[string]string)
for _, e := range entries {
if val := os.Getenv(e.envVar); val != "" {
metadata[e.key] = val
}
}
if len(metadata) == 0 {
return nil
}
return metadata
}