1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-playground synced 2026-07-18 23:20:18 +00:00

WIP: image workflow

Signed-off-by: Jason Hall <jason@chainguard.dev>
This commit is contained in:
Jason Hall 2023-06-22 20:13:52 -04:00
parent 8c0c13a582
commit 36810cf5ac
Failed to extract signature
12 changed files with 583 additions and 1 deletions

View file

@ -0,0 +1,67 @@
/*
Copyright 2022 Chainguard, Inc.
SPDX-License-Identifier: Apache-2.0
*/
package main
// NOTE: these types will eventually be made available as part of a Chainguard
// SDK along with our API clients.
// Occurrence is the CloudEvent payload for events.
type Occurrence struct {
Actor *Actor `json:"actor,omitempty"`
// Body is the resource that was created.
// For this sample, it will always be RegistryPush.
Body RegistryPush `json:"body,omitempty"`
}
// Actor is the event payload form of which identity was responsible for the
// event.
type Actor struct {
// Subject is the identity that triggered this event.
Subject string `json:"subject"`
// Actor contains the name/value pairs for each of the claims that were
// validated to assume the identity whose UIDP appears in Subject above.
Actor map[string]string `json:"act,omitempty"`
}
// ChangedEventType is the cloudevents event type for registry push events.
const PushEventType = "dev.chainguard.registry.push.v1"
// RegistryPush describes an item being pushed to the registry.
type RegistryPush struct {
// Repository identifies the repository being pushed
Repository string `json:"repository"`
// Tag holds the tag being pushed, if there is one.
Tag string `json:"tag,omitempty"`
// Digest holds the digest being pushed.
// Digest will hold the sha256 of the content being pushed, whether that is
// a blob or a manifest.
Digest string `json:"digest"`
// Type determines whether the object being pushed is a manifest or blob.
Type string `json:"type"`
// When holds when the push occurred.
//When civil.DateTime `json:"when"`
// Location holds the detected approximate location of the client who pulled.
// For example, "ColumbusOHUS" or "Minato City13JP".
Location string `json:"location"`
// UserAgent holds the user-agent of the client who pulled.
UserAgent string `json:"user_agent" bigquery:"user_agent"`
Error *Error `json:"error,omitempty"`
}
type Error struct {
Status int `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
}

View file

@ -0,0 +1,117 @@
/*
Copyright 2022 Chainguard, Inc.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"context"
"log"
"net/http"
"strings"
cloudevents "github.com/cloudevents/sdk-go/v2"
cehttp "github.com/cloudevents/sdk-go/v2/protocol/http"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/google/go-github/v43/github"
"github.com/kelseyhightower/envconfig"
"golang.org/x/oauth2"
)
type envConfig struct {
Issuer string `envconfig:"ISSUER_URL" required:"true"`
Group string `envconfig:"GROUP" required:"true"`
Port int `envconfig:"PORT" default:"8080" required:"true"`
GithubOrg string `envconfig:"GITHUB_ORG" required:"true"`
GithubRepo string `envconfig:"GITHUB_REPO" required:"true"`
GithubToken string `envconfig:"GITHUB_TOKEN" required:"true"`
GithubWorkflowID string `envconfig:"GITHUB_WORKFLOW_ID" required:"true"`
}
func main() {
var env envConfig
if err := envconfig.Process("", &env); err != nil {
log.Fatalf("failed to process env var: %s", err)
}
client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: strings.TrimSpace(env.GithubToken)},
)))
c, err := cloudevents.NewClientHTTP(cloudevents.WithPort(env.Port),
// We need to infuse the request onto context, so we can
// authenticate requests.
cehttp.WithRequestDataAtContextMiddleware())
if err != nil {
log.Fatalf("failed to create client, %v", err)
}
ctx := context.Background()
// Construct a verifier that ensures tokens are issued by the Chainguard
// issuer we expect and are intended for a customer webhook.
provider, err := oidc.NewProvider(ctx, env.Issuer)
if err != nil {
log.Fatalf("failed to create provider: %v", err)
}
verifier := provider.Verifier(&oidc.Config{
ClientID: "customer",
})
receiver := func(ctx context.Context, event cloudevents.Event) error {
// We expect Chainguard webhooks to pass an Authorization header.
auth := strings.TrimPrefix(cehttp.RequestDataFromContext(ctx).Header.Get("Authorization"), "Bearer ")
if auth == "" {
return cloudevents.NewHTTPResult(http.StatusUnauthorized, "Unauthorized")
}
// Verify that the token is well-formed, and in fact intended for us!
if tok, err := verifier.Verify(ctx, auth); err != nil {
return cloudevents.NewHTTPResult(http.StatusForbidden, "unable to verify token: %w", err)
} else if !strings.HasPrefix(tok.Subject, "webhook:") {
return cloudevents.NewHTTPResult(http.StatusForbidden, "subject should be from the Chainguard webhook component, got: %s", tok.Subject)
} else if group := strings.TrimPrefix(tok.Subject, "webhook:"); group != env.Group {
return cloudevents.NewHTTPResult(http.StatusForbidden, "this token is intended for %s, wanted one for %s", group, env.Group)
}
// We are handling a specific event type, so filter the rest.
if event.Type() != PushEventType {
return nil
}
data := Occurrence{}
if err := event.DataAs(&data); err != nil {
return cloudevents.NewHTTPResult(http.StatusInternalServerError, "unable to unmarshal data: %w", err)
}
log.Printf("got event: %+v", data)
img := "cgr.dev/" + data.Body.Repository
log.Println("got event for image:", img)
if _, err := client.Actions.CreateWorkflowDispatchEventByFileName(ctx, env.GithubOrg, env.GithubRepo, env.GithubWorkflowID, github.CreateWorkflowDispatchEventRequest{
Ref: "main",
Inputs: map[string]interface{}{
"image": img,
},
}); err != nil {
log.Printf("failed to dispatch workflow: %v", err)
return cloudevents.NewHTTPResult(http.StatusInternalServerError, "failed to dispatch workflow: %w", err)
}
log.Printf("dispatched workflow %s (image=%q)", env.GithubWorkflowID, img)
return nil
}
if err := c.StartReceiver(ctx, func(ctx context.Context, event cloudevents.Event) error {
// This thunk simply wraps the main receiver in one that logs any errors
// we encounter.
err := receiver(ctx, event)
if err != nil {
log.Printf("SAW: %v", err)
}
return err
}); err != nil {
log.Fatal(err)
}
}