diff --git a/extension/notification/webhook/webhook_test.go b/extension/notification/webhook/webhook_test.go new file mode 100644 index 00000000..e0f6b776 --- /dev/null +++ b/extension/notification/webhook/webhook_test.go @@ -0,0 +1,59 @@ +package webhook + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/rusenask/keel/types" +) + +func TestWebhookRequest(t *testing.T) { + currentTime := time.Now() + handler := func(resp http.ResponseWriter, req *http.Request) { + body, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("failed to parse body: %s", err) + } + + bodyStr := string(body) + + if !strings.Contains(bodyStr, types.NotificationPreDeploymentUpdate.String()) { + t.Errorf("missing deployment type") + } + + if !strings.Contains(bodyStr, "LevelDebug") { + t.Errorf("missing level") + } + + if !strings.Contains(bodyStr, "update deployment") { + t.Errorf("missing name") + } + if !strings.Contains(bodyStr, "message here") { + t.Errorf("missing message") + } + + t.Log(bodyStr) + + } + + // create test server with handler + ts := httptest.NewServer(http.HandlerFunc(handler)) + defer ts.Close() + + s := &sender{ + endpoint: ts.URL, + client: &http.Client{}, + } + + s.Send(types.EventNotification{ + Name: "update deployment", + Message: "message here", + CreatedAt: currentTime, + Type: types.NotificationPreDeploymentUpdate, + Level: types.LevelDebug, + }) +} diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 573c3a76..82b485f4 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -165,6 +165,14 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated continue } + p.sender.Send(types.EventNotification{ + Name: "preparing to update deployment", + Message: fmt.Sprintf("Preparing to update deployment %s/%s (%s)", deployment.Namespace, deployment.Name, strings.Join(getImages(&deployment), ", ")), + CreatedAt: time.Now(), + Type: types.NotificationPreDeploymentUpdate, + Level: types.LevelDebug, + }) + err = p.implementer.Update(&deployment) if err != nil { log.WithFields(log.Fields{ diff --git a/trigger/http/http.go b/trigger/http/http.go index dcab1468..a841b99f 100644 --- a/trigger/http/http.go +++ b/trigger/http/http.go @@ -100,17 +100,5 @@ func (s *TriggerServer) versionHandler(resp http.ResponseWriter, req *http.Reque } func (s *TriggerServer) trigger(event types.Event) error { - s.providers.Submit(event) - // for _, p := range s.providers { - // err := p.Submit(event) - // if err != nil { - // log.WithFields(log.Fields{ - // "error": err, - // "provider": p.GetName(), - // "trigger": event.TriggerName, - // }).Error("trigger.trigger: got error while submitting event to provider") - // } - // } - - return nil + return s.providers.Submit(event) } diff --git a/trigger/pubsub/pubsub.go b/trigger/pubsub/pubsub.go index 78a56040..16e9cb4e 100644 --- a/trigger/pubsub/pubsub.go +++ b/trigger/pubsub/pubsub.go @@ -13,7 +13,7 @@ import ( "github.com/rusenask/keel/provider" "github.com/rusenask/keel/types" - "github.com/rusenask/keel/util/version" + "github.com/rusenask/keel/util/image" log "github.com/Sirupsen/logrus" ) @@ -160,24 +160,26 @@ func (s *PubsubSubscriber) callback(ctx context.Context, msg *pubsub.Message) { return } - imageName, parsedVersion, err := version.GetImageNameAndVersion(decoded.Tag) + ref, err := image.Parse(decoded.Tag) + + // imageName, parsedVersion, err := version.GetImageNameAndVersion(decoded.Tag) if err != nil { log.WithFields(log.Fields{ "action": decoded.Action, "tag": decoded.Tag, "error": err, - }).Warn("trigger.pubsub: failed to get name and version from image") + }).Warn("trigger.pubsub: failed to parse image name") return } // sending event to the providers log.WithFields(log.Fields{ - "action": decoded.Action, - "tag": decoded.Tag, - "version": parsedVersion.String(), + "action": decoded.Action, + "tag": ref.Tag(), + "image_name": ref.Name(), }).Debug("trigger.pubsub: got message") event := types.Event{ - Repository: types.Repository{Name: imageName, Tag: parsedVersion.String()}, + Repository: types.Repository{Name: ref.Repository(), Tag: ref.Tag()}, CreatedAt: time.Now(), } diff --git a/trigger/pubsub/pubsub_test.go b/trigger/pubsub/pubsub_test.go index d487f2c6..572e1be7 100644 --- a/trigger/pubsub/pubsub_test.go +++ b/trigger/pubsub/pubsub_test.go @@ -57,3 +57,54 @@ func TestCallback(t *testing.T) { } } +func TestCallbackTagNotSemver(t *testing.T) { + + fp := &fakeProvider{} + providers := provider.New([]provider.Provider{fp}) + sub := &PubsubSubscriber{disableAck: true, providers: providers} + + dataMsg := &Message{Action: "INSERT", Tag: "gcr.io/stemnapp/alpine-website:latest"} + data, _ := json.Marshal(dataMsg) + + msg := &pubsub.Message{Data: data} + + sub.callback(context.Background(), msg) + + if len(fp.submitted) == 0 { + t.Fatalf("no events found in provider") + } + if fp.submitted[0].Repository.Name != "gcr.io/stemnapp/alpine-website" { + t.Errorf("expected repo name %s but got %s", "gcr.io/v2-namespace/hello-world", fp.submitted[0].Repository.Name) + } + + if fp.submitted[0].Repository.Tag != "latest" { + t.Errorf("expected repo tag %s but got %s", "latest", fp.submitted[0].Repository.Tag) + } + +} + +func TestCallbackNoTag(t *testing.T) { + + fp := &fakeProvider{} + providers := provider.New([]provider.Provider{fp}) + sub := &PubsubSubscriber{disableAck: true, providers: providers} + + dataMsg := &Message{Action: "INSERT", Tag: "gcr.io/stemnapp/alpine-website"} + data, _ := json.Marshal(dataMsg) + + msg := &pubsub.Message{Data: data} + + sub.callback(context.Background(), msg) + + if len(fp.submitted) == 0 { + t.Fatalf("no events found in provider") + } + if fp.submitted[0].Repository.Name != "gcr.io/stemnapp/alpine-website" { + t.Errorf("expected repo name %s but got %s", "gcr.io/v2-namespace/hello-world", fp.submitted[0].Repository.Name) + } + + if fp.submitted[0].Repository.Tag != "latest" { + t.Errorf("expected repo tag %s but got %s", "latest", fp.submitted[0].Repository.Tag) + } + +} diff --git a/types/level_jsonenums.go b/types/level_jsonenums.go new file mode 100644 index 00000000..f690c1cc --- /dev/null +++ b/types/level_jsonenums.go @@ -0,0 +1,68 @@ +// generated by jsonenums -type=Level; DO NOT EDIT + +package types + +import ( + "encoding/json" + "fmt" +) + +var ( + _LevelNameToValue = map[string]Level{ + "LevelDebug": LevelDebug, + "LevelInfo": LevelInfo, + "LevelSuccess": LevelSuccess, + "LevelWarn": LevelWarn, + "LevelError": LevelError, + "LevelFatal": LevelFatal, + } + + _LevelValueToName = map[Level]string{ + LevelDebug: "LevelDebug", + LevelInfo: "LevelInfo", + LevelSuccess: "LevelSuccess", + LevelWarn: "LevelWarn", + LevelError: "LevelError", + LevelFatal: "LevelFatal", + } +) + +func init() { + var v Level + if _, ok := interface{}(v).(fmt.Stringer); ok { + _LevelNameToValue = map[string]Level{ + interface{}(LevelDebug).(fmt.Stringer).String(): LevelDebug, + interface{}(LevelInfo).(fmt.Stringer).String(): LevelInfo, + interface{}(LevelSuccess).(fmt.Stringer).String(): LevelSuccess, + interface{}(LevelWarn).(fmt.Stringer).String(): LevelWarn, + interface{}(LevelError).(fmt.Stringer).String(): LevelError, + interface{}(LevelFatal).(fmt.Stringer).String(): LevelFatal, + } + } +} + +// MarshalJSON is generated so Level satisfies json.Marshaler. +func (r Level) MarshalJSON() ([]byte, error) { + if s, ok := interface{}(r).(fmt.Stringer); ok { + return json.Marshal(s.String()) + } + s, ok := _LevelValueToName[r] + if !ok { + return nil, fmt.Errorf("invalid Level: %d", r) + } + return json.Marshal(s) +} + +// UnmarshalJSON is generated so Level satisfies json.Unmarshaler. +func (r *Level) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("Level should be a string, got %s", data) + } + v, ok := _LevelNameToValue[s] + if !ok { + return fmt.Errorf("invalid Level %q", s) + } + *r = v + return nil +} diff --git a/types/notification_jsonenums.go b/types/notification_jsonenums.go new file mode 100644 index 00000000..ea0f39d6 --- /dev/null +++ b/types/notification_jsonenums.go @@ -0,0 +1,62 @@ +// generated by jsonenums -type=Notification; DO NOT EDIT + +package types + +import ( + "encoding/json" + "fmt" +) + +var ( + _NotificationNameToValue = map[string]Notification{ + "PreProviderSubmitNotification": PreProviderSubmitNotification, + "PostProviderSubmitNotification": PostProviderSubmitNotification, + "NotificationPreDeploymentUpdate": NotificationPreDeploymentUpdate, + "NotificationDeploymentUpdate": NotificationDeploymentUpdate, + } + + _NotificationValueToName = map[Notification]string{ + PreProviderSubmitNotification: "PreProviderSubmitNotification", + PostProviderSubmitNotification: "PostProviderSubmitNotification", + NotificationPreDeploymentUpdate: "NotificationPreDeploymentUpdate", + NotificationDeploymentUpdate: "NotificationDeploymentUpdate", + } +) + +func init() { + var v Notification + if _, ok := interface{}(v).(fmt.Stringer); ok { + _NotificationNameToValue = map[string]Notification{ + interface{}(PreProviderSubmitNotification).(fmt.Stringer).String(): PreProviderSubmitNotification, + interface{}(PostProviderSubmitNotification).(fmt.Stringer).String(): PostProviderSubmitNotification, + interface{}(NotificationPreDeploymentUpdate).(fmt.Stringer).String(): NotificationPreDeploymentUpdate, + interface{}(NotificationDeploymentUpdate).(fmt.Stringer).String(): NotificationDeploymentUpdate, + } + } +} + +// MarshalJSON is generated so Notification satisfies json.Marshaler. +func (r Notification) MarshalJSON() ([]byte, error) { + if s, ok := interface{}(r).(fmt.Stringer); ok { + return json.Marshal(s.String()) + } + s, ok := _NotificationValueToName[r] + if !ok { + return nil, fmt.Errorf("invalid Notification: %d", r) + } + return json.Marshal(s) +} + +// UnmarshalJSON is generated so Notification satisfies json.Unmarshaler. +func (r *Notification) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("Notification should be a string, got %s", data) + } + v, ok := _NotificationNameToValue[s] + if !ok { + return fmt.Errorf("invalid Notification %q", s) + } + *r = v + return nil +} diff --git a/types/types.go b/types/types.go index 79a5b816..a99e10d5 100644 --- a/types/types.go +++ b/types/types.go @@ -1,3 +1,5 @@ +//go:generate jsonenums -type=Notification +//go:generate jsonenums -type=Level package types import ( @@ -146,11 +148,11 @@ const ( // EventNotification notification used for sending type EventNotification struct { - Name string `json:"name,omitempty"` - Message string `json:"message,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty"` - Type Notification `json:"type,omitempty"` - Level Level `json:"level,omitempty"` + Name string `json:"name"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` + Type Notification `json:"type"` + Level Level `json:"level"` } // Notification - notification types used by notifier @@ -161,6 +163,7 @@ const ( PreProviderSubmitNotification Notification = iota PostProviderSubmitNotification + NotificationPreDeploymentUpdate NotificationDeploymentUpdate ) @@ -170,6 +173,8 @@ func (n Notification) String() string { return "pre provider submit" case PostProviderSubmitNotification: return "post provider submit" + case NotificationPreDeploymentUpdate: + return "preparing deployment update" case NotificationDeploymentUpdate: return "deployment update" default: @@ -180,7 +185,8 @@ func (n Notification) String() string { type Level int const ( - LevelInfo Level = iota + LevelDebug Level = iota + LevelInfo LevelSuccess LevelWarn LevelError