Skip to content
This repository was archived by the owner on May 21, 2024. It is now read-only.

Commit ea82051

Browse files
authored
Merge pull request #5 from benjlevesque/gcr-pubsub
Add support for GCR
2 parents 4635fae + 8d9188b commit ea82051

File tree

19 files changed

+476
-116
lines changed

19 files changed

+476
-116
lines changed

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Usage:
1717
Available Commands:
1818
help Help about any command
1919
http update repositories in response to image hooks
20+
pubsub update repositories in response to gcr pubsub events
2021
update update a repository configuration
2122

2223
Flags:
@@ -25,9 +26,9 @@ Flags:
2526
Use "image-updater [command] --help" for more information about a command.
2627
```
2728

28-
There are two sub-commands, `http` and `update`.
29+
There are three sub-commands, `http`, `pubsub` and `update`.
2930

30-
`http` provides a Webhook service, and `update` will perform the same
31+
`http` provides a Webhook service, `pubsub` subscribes to pubsub events and `update` will perform the same
3132
functionality from the command-line.
3233

3334
## Update tool
@@ -57,15 +58,22 @@ This is a micro-service for updating Git Repos when a hook is received indicatin
5758

5859
This currently supports receiving hooks from Docker and Quay.io.
5960

60-
## WARNING
61+
### WARNING
6162

6263
Neither Docker Hub nor Quay.io provide a way for receivers to authenticate Webhooks, which makes this insecure, a malicious user could trigger the creation of pull requests in your git hosting service.
6364

6465
Please understand the risks of using this component.
6566

67+
## Pubsub Service
68+
Similarly to the Webhook service, the pubsub services allows to update Git Repos when a pubsub Event is received.
69+
70+
This currently supports Events from [Google Cloud Registry](https://cloud.google.com/container-registry/docs/configuring-notifications).
71+
72+
It requires two arguments `--project-id` and `--subscription-name`. See [below](#google-container-registry-setup) for more details on how to setup the subscription.
73+
6674
## Configuration
6775

68-
This service uses a really simple configuration:
76+
Both the Webhook and Pubsub service uses a really simple configuration:
6977

7078
```yaml
7179
repositories:
@@ -133,6 +141,7 @@ changed to support Quay.io.
133141
The `--parser` command-line option chooses which of the supported (Quay, Docker)
134142
hook formats to parse.
135143

144+
136145
## Exposing the Handler
137146

138147
The Service exposes a Hook handler at `/` on port 8080 that handles the
@@ -143,6 +152,22 @@ configured hook type.
143152
A Tekton task is provided in [./tekton](./tekton) which allows you to apply
144153
updates to repos from a Tekton pipeline run.
145154

155+
156+
## Google Container registry setup
157+
```bash
158+
gcloud pubsub topics create gcr
159+
gcloud pubsub subscriptions create gcr-image-updater --topic projects/$GOOGLE_PROJECT/topics/gcr
160+
161+
gcloud iam service-accounts create
162+
gcloud iam service-accounts keys create credentials.json \
163+
--iam-account $SA_NAME@$GOOGLE_PROJECT.iam.gserviceaccount.com
164+
165+
gcloud pubsub subscriptions add-iam-policy-binding gcr-image-updater \
166+
--member=serviceAccount:$SA_NAME@$GOOGLE_PROJECT.iam.gserviceaccount.com --role=roles/pubsub.subscriber
167+
```
168+
169+
You then need to set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path the generated `credentials.json` file.
170+
146171
## Building
147172

148173
A `Dockerfile` is provided for building a container, but otherwise:

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/gitops-tools/image-updater
33
go 1.14
44

55
require (
6+
cloud.google.com/go/pubsub v1.0.1
67
github.com/gitops-tools/pkg v0.0.0-20200823054310-42f81b2b396d
78
github.com/go-logr/logr v0.1.0
89
github.com/go-logr/zapr v0.1.0

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR
44
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
55
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
66
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7+
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
78
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
89
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
910
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
1011
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
12+
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
1113
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
1214
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
1315
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -192,6 +194,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
192194
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
193195
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
194196
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
197+
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
195198
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
196199
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
197200
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
@@ -227,6 +230,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
227230
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
228231
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
229232
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
233+
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
230234
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
231235
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
232236
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -422,6 +426,7 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
422426
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
423427
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
424428
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
429+
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
425430
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
426431
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
427432
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -504,6 +509,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
504509
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
505510
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
506511
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
512+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
507513
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508514
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
509515
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -582,6 +588,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt
582588
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
583589
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
584590
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
591+
google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA=
585592
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
586593
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
587594
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -604,6 +611,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
604611
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
605612
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
606613
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
614+
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
607615
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
608616
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
609617
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

pkg/cmd/pubsub.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"cloud.google.com/go/pubsub"
9+
"github.com/gitops-tools/image-updater/pkg/applier"
10+
"github.com/gitops-tools/image-updater/pkg/config"
11+
"github.com/gitops-tools/image-updater/pkg/hooks/gcr"
12+
"github.com/gitops-tools/image-updater/pkg/pubsubhandler"
13+
"github.com/gitops-tools/pkg/client"
14+
"github.com/go-logr/zapr"
15+
"github.com/spf13/cobra"
16+
"github.com/spf13/viper"
17+
"go.uber.org/zap"
18+
)
19+
20+
const (
21+
projectIDFlag = "project-id"
22+
subscriptionNameFlag = "subscription-name"
23+
)
24+
25+
type message struct {
26+
data []byte
27+
}
28+
29+
func (m *message) Ack() {}
30+
func (m *message) Data() []byte { return m.data }
31+
32+
func makePubsubCmd() *cobra.Command {
33+
cmd := &cobra.Command{
34+
Use: "pubsub",
35+
Short: "update repositories in response to gcr pubsub events",
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
zapl, _ := zap.NewProduction()
38+
defer func() {
39+
_ = zapl.Sync() // flushes buffer, if any
40+
}()
41+
logger := zapr.NewLogger(zapl)
42+
scmClient, err := createClientFromViper()
43+
if err != nil {
44+
return fmt.Errorf("failed to create a git driver: %s", err)
45+
}
46+
f, err := os.Open(viper.GetString("config"))
47+
if err != nil {
48+
return err
49+
}
50+
defer f.Close()
51+
repos, err := config.Parse(f)
52+
if err != nil {
53+
return err
54+
}
55+
applier := applier.New(logger, client.New(scmClient), repos)
56+
57+
sub, err := createSubscriptionFromViper()
58+
if err != nil {
59+
return err
60+
}
61+
62+
handler := pubsubhandler.New(logger, applier, gcr.Parse)
63+
64+
return sub.Receive(context.Background(), func(ctx context.Context, msg *pubsub.Message) {
65+
handler.Handle(ctx, &message{
66+
data: msg.Data,
67+
})
68+
})
69+
},
70+
}
71+
72+
cmd.Flags().String(
73+
"config",
74+
"/etc/image-updater/config.yaml",
75+
"repository configuration",
76+
)
77+
logIfError(viper.BindPFlag("config", cmd.Flags().Lookup("config")))
78+
79+
cmd.Flags().String(
80+
projectIDFlag,
81+
"",
82+
"GCP project ID",
83+
)
84+
logIfError(viper.BindPFlag(projectIDFlag, cmd.Flags().Lookup(projectIDFlag)))
85+
logIfError(cmd.MarkFlagRequired(projectIDFlag))
86+
87+
cmd.Flags().String(
88+
subscriptionNameFlag,
89+
"",
90+
"GCP subscription name",
91+
)
92+
logIfError(viper.BindPFlag(subscriptionNameFlag, cmd.Flags().Lookup(subscriptionNameFlag)))
93+
logIfError(cmd.MarkFlagRequired(subscriptionNameFlag))
94+
95+
return cmd
96+
}
97+
98+
func createSubscriptionFromViper() (*pubsub.Subscription, error) {
99+
ctx := context.Background()
100+
projectID := viper.GetString(projectIDFlag)
101+
subscriptionName := viper.GetString(subscriptionNameFlag)
102+
103+
client, err := pubsub.NewClient(ctx, projectID)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
sub := client.Subscription(subscriptionName)
109+
return sub, nil
110+
}

pkg/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func makeRootCmd() *cobra.Command {
6060

6161
cmd.AddCommand(makeHTTPCmd())
6262
cmd.AddCommand(makeUpdateCmd())
63+
cmd.AddCommand(makePubsubCmd())
6364
return cmd
6465
}
6566

pkg/handler/handler.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handler
22

33
import (
4+
"io/ioutil"
45
"net/http"
56

67
"github.com/go-logr/logr"
@@ -17,14 +18,12 @@ type Handler struct {
1718
}
1819

1920
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
20-
h.log.Info("processing hook request")
21-
hook, err := h.parser(r)
21+
hook, err := h.parse(r)
2222
if err != nil {
2323
h.log.Error(err, "failed to parse request")
2424
http.Error(w, err.Error(), http.StatusInternalServerError)
2525
return
2626
}
27-
2827
err = h.applier.UpdateFromHook(r.Context(), hook)
2928

3029
if err != nil {
@@ -34,6 +33,17 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3433
}
3534
}
3635

36+
func (h *Handler) parse(r *http.Request) (hooks.PushEvent, error) {
37+
h.log.Info("processing hook request")
38+
// TODO: LimitReader
39+
data, err := ioutil.ReadAll(r.Body)
40+
if err != nil {
41+
h.log.Error(err, "failed to read request body")
42+
return nil, err
43+
}
44+
return h.parser(data)
45+
}
46+
3747
// New creates and returns a new Handler.
3848
func New(logger logr.Logger, u *applier.Applier, p hooks.PushEventParser) *Handler {
3949
return &Handler{log: logger, applier: u, parser: p}

pkg/handler/handler_test.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestHandler(t *testing.T) {
4949
}
5050

5151
func TestHandlerWithParseFailure(t *testing.T) {
52-
badParser := func(*http.Request) (hooks.PushEvent, error) {
52+
badParser := func(payload []byte) (hooks.PushEvent, error) {
5353
return nil, errors.New("failed")
5454
}
5555
logger := zapr.NewLogger(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)))
@@ -85,6 +85,36 @@ func TestHandlerWithFailureToUpdate(t *testing.T) {
8585
}
8686
}
8787

88+
func TestParseWithNoBody(t *testing.T) {
89+
logger := zapr.NewLogger(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)))
90+
m := mock.New(t)
91+
applier := applier.New(logger, m, createConfigs(), updater.NameGenerator(stubNameGenerator{"a"}))
92+
h := New(logger, applier, quay.Parse)
93+
bodyErr := errors.New("just a test error")
94+
95+
req := httptest.NewRequest("POST", "/", failingReader{err: bodyErr})
96+
97+
_, err := h.parse(req)
98+
if err != bodyErr {
99+
t.Fatal("expected an error")
100+
}
101+
}
102+
103+
func TestParseWithUnparseableBody(t *testing.T) {
104+
logger := zapr.NewLogger(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)))
105+
m := mock.New(t)
106+
applier := applier.New(logger, m, createConfigs(), updater.NameGenerator(stubNameGenerator{"a"}))
107+
h := New(logger, applier, quay.Parse)
108+
109+
req := httptest.NewRequest("POST", "/", nil)
110+
111+
_, err := h.parse(req)
112+
113+
if err == nil {
114+
t.Fatal("expected an error")
115+
}
116+
}
117+
88118
func makeHookRequest(t *testing.T, fixture string) *http.Request {
89119
t.Helper()
90120
b, err := ioutil.ReadFile(fixture)
@@ -118,3 +148,14 @@ type stubNameGenerator struct {
118148
func (s stubNameGenerator) PrefixedName(p string) string {
119149
return p + s.name
120150
}
151+
152+
type failingReader struct {
153+
err error
154+
}
155+
156+
func (f failingReader) Read(p []byte) (n int, err error) {
157+
return 0, f.err
158+
}
159+
func (f failingReader) Close() error {
160+
return f.err
161+
}

pkg/hooks/docker/hook.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,14 @@ package docker
33
import (
44
"encoding/json"
55
"fmt"
6-
"io/ioutil"
7-
"net/http"
86

97
"github.com/gitops-tools/image-updater/pkg/hooks"
108
)
119

12-
// Parse takes an http.Request and parses it into a Docker webhook event.
13-
func Parse(req *http.Request) (hooks.PushEvent, error) {
14-
// TODO: LimitReader
15-
data, err := ioutil.ReadAll(req.Body)
16-
if err != nil {
17-
return nil, err
18-
}
10+
// Parse parses a payload into a Docker webhook event.
11+
func Parse(payload []byte) (hooks.PushEvent, error) {
1912
h := &Webhook{}
20-
err = json.Unmarshal(data, h)
13+
err := json.Unmarshal(payload, h)
2114
if err != nil {
2215
return nil, err
2316
}

0 commit comments

Comments
 (0)