diff --git a/internal/webhook/event/common.go b/internal/webhook/event/common.go new file mode 100644 index 00000000..f807e03a --- /dev/null +++ b/internal/webhook/event/common.go @@ -0,0 +1,62 @@ +package event + +import ( + "path/filepath" + "strings" + + configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const PullRequestOpened = "opened" +const PullRequestClosed = "closed" + +type ChangeInfo struct { + ShaBefore string + ShaAfter string +} + +type Event interface { + Handle(client.Client, []configv1alpha1.TerraformRepository, []configv1alpha1.TerraformLayer) error +} + +func GetNormalizedURL(urls []string) string { + return "" +} + +func ParseRevision(ref string) string { + refParts := strings.SplitN(ref, "/", 3) + return refParts[len(refParts)-1] +} + +func isLayerLinkedToAnyRepositories(repositories []configv1alpha1.TerraformRepository, layer configv1alpha1.TerraformLayer) bool { + for _, r := range repositories { + if r.Name == layer.Spec.Repository.Name && r.Namespace == layer.Spec.Repository.Namespace { + return true + } + } + return false +} + +func layerFilesHaveChanged(layer configv1alpha1.TerraformLayer, changedFiles []string) bool { + if len(changedFiles) == 0 { + return true + } + + // At last one changed file must be under refresh path + for _, f := range changedFiles { + f = ensureAbsPath(f) + if strings.Contains(f, layer.Spec.Path) { + return true + } + } + + return false +} + +func ensureAbsPath(input string) string { + if !filepath.IsAbs(input) { + return string(filepath.Separator) + input + } + return input +} diff --git a/internal/webhook/event/event.go b/internal/webhook/event/event.go deleted file mode 100644 index f650d52c..00000000 --- a/internal/webhook/event/event.go +++ /dev/null @@ -1,95 +0,0 @@ -package event - -import ( - "context" - "path/filepath" - "strings" - - configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" - "github.com/padok-team/burrito/internal/annotations" - log "github.com/sirupsen/logrus" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const PullRequestOpened = "opened" -const PullRequestClosed = "closed" - -type Event interface { - Handle(client.Client, *configv1alpha1.TerraformLayerList) error -} - -type PushEvent struct { - URL string - Revision string - ChangeInfo - Changes []string -} - -type ChangeInfo struct { - ShaBefore string - ShaAfter string -} - -type PullRequestEvent struct { - URL string - Revision string - Action string - Changes []string -} - -func GetNormalizedURL(urls []string) string { - return "" -} - -func ParseRevision(ref string) string { - refParts := strings.SplitN(ref, "/", 3) - return refParts[len(refParts)-1] -} - -func (e *PushEvent) Handle(c client.Client, layers *configv1alpha1.TerraformLayerList) error { - for _, layer := range layers.Items { - ann := map[string]string{} - log.Printf("evaluating terraform layer %s for revision %s", layer.Name, e.Revision) - if layer.Spec.Branch != e.Revision { - log.Infof("branch %s for terraform layer %s not matching revision %s", layer.Spec.Branch, layer.Name, e.Revision) - continue - } - ann[annotations.LastBranchCommit] = e.ChangeInfo.ShaAfter - if layerFilesHaveChanged(&layer, e.Changes) { - ann[annotations.LastConcerningCommit] = e.ChangeInfo.ShaAfter - } - err := annotations.Add(context.TODO(), c, layer, ann) - if err != nil { - log.Errorf("could not add annotation to terraform layer %s", err) - return err - } - } - return nil -} - -func (e *PullRequestEvent) Handle(c client.Client, layers *configv1alpha1.TerraformLayerList) error { - return nil -} - -func layerFilesHaveChanged(layer *configv1alpha1.TerraformLayer, changedFiles []string) bool { - if len(changedFiles) == 0 { - return true - } - - // At last one changed file must be under refresh path - for _, f := range changedFiles { - f = ensureAbsPath(f) - if strings.Contains(f, layer.Spec.Path) { - return true - } - } - - return false -} - -func ensureAbsPath(input string) string { - if !filepath.IsAbs(input) { - return string(filepath.Separator) + input - } - return input -} diff --git a/internal/webhook/event/pull.go b/internal/webhook/event/pull.go new file mode 100644 index 00000000..b5c3a4e6 --- /dev/null +++ b/internal/webhook/event/pull.go @@ -0,0 +1,64 @@ +package event + +import ( + "fmt" + + configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type PullRequestEvent struct { + URL string + ProviderLabel string + Revision string + Action string + Base string + ID string + Changes []string +} + +func (e *PullRequestEvent) getAffectedLayers(allLayers []configv1alpha1.TerraformLayer, allRepos []configv1alpha1.TerraformRepository) []configv1alpha1.TerraformLayer { + affectedRepositories := []configv1alpha1.TerraformRepository{} + for _, repo := range allRepos { + log.Infof("evaluating terraform repository %s for url %s", repo.Name, repo.Spec.Repository.Url) + if e.URL == GetNormalizedURL([]string{repo.Spec.Repository.Url}) { + affectedRepositories = append(affectedRepositories, repo) + continue + } + } + layers := []configv1alpha1.TerraformLayer{} + for _, layer := range allLayers { + eventIsRelevant := layerFilesHaveChanged(layer, e.Changes) && isLayerLinkedToAnyRepositories(affectedRepositories, layer) && layer.Spec.Branch == e.Base + if eventIsRelevant { + layers = append(layers, layer) + } + } + return layers +} +func (e *PullRequestEvent) Handle(c client.Client, allRepositories []configv1alpha1.TerraformRepository, allLayers []configv1alpha1.TerraformLayer) error { + layers := e.getAffectedLayers(allLayers, allRepositories) + for _, layer := range layers { + switch e.Action { + case PullRequestOpened: + + case PullRequestClosed: + default: + log.Infof("pull request event %s is not supported", e.Action) + return nil + } + } + return nil +} + +func (e *PullRequestEvent) getNewPullRequestLayer(layer configv1alpha1.TerraformLayer) *configv1alpha1.TerraformLayer { + newLayer := layer.DeepCopy() + newLayer.Spec.Branch = e.Revision + newLayer.GenerateName = fmt.Sprintf("%s-pr-", layer.Name) + newLayer.Name = "" + newLayer.Labels["pull-request-event"] = "true" + newLayer.Labels[e.ProviderLabel] = e.ID + newLayer.Spec.RemediationStrategy = "dry" + newLayer.Spec.PlanOnPullRequest = false + return newLayer +} diff --git a/internal/webhook/event/push.go b/internal/webhook/event/push.go new file mode 100644 index 00000000..84855b8a --- /dev/null +++ b/internal/webhook/event/push.go @@ -0,0 +1,57 @@ +package event + +import ( + "context" + + configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" + "github.com/padok-team/burrito/internal/annotations" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type PushEvent struct { + URL string + Revision string + ChangeInfo + Changes []string +} + +func (e *PushEvent) Handle(c client.Client, allRepositories []configv1alpha1.TerraformRepository, allLayers []configv1alpha1.TerraformLayer) error { + layers := e.getAffectedLayers(allLayers, allRepositories) + for _, layer := range layers { + ann := map[string]string{} + log.Printf("evaluating terraform layer %s for revision %s", layer.Name, e.Revision) + if layer.Spec.Branch != e.Revision { + log.Infof("branch %s for terraform layer %s not matching revision %s", layer.Spec.Branch, layer.Name, e.Revision) + continue + } + ann[annotations.LastBranchCommit] = e.ChangeInfo.ShaAfter + if layerFilesHaveChanged(layer, e.Changes) { + ann[annotations.LastConcerningCommit] = e.ChangeInfo.ShaAfter + } + err := annotations.Add(context.TODO(), c, layer, ann) + if err != nil { + log.Errorf("could not add annotation to terraform layer %s", err) + return err + } + } + return nil +} + +func (e *PushEvent) getAffectedLayers(allLayers []configv1alpha1.TerraformLayer, allRepos []configv1alpha1.TerraformRepository) []configv1alpha1.TerraformLayer { + affectedRepositories := []configv1alpha1.TerraformRepository{} + for _, repo := range allRepos { + log.Infof("evaluating terraform repository %s for url %s", repo.Name, repo.Spec.Repository.Url) + if e.URL == GetNormalizedURL([]string{repo.Spec.Repository.Url}) { + affectedRepositories = append(affectedRepositories, repo) + continue + } + } + layers := []configv1alpha1.TerraformLayer{} + for _, layer := range allLayers { + if layerFilesHaveChanged(layer, e.Changes) && isLayerLinkedToAnyRepositories(affectedRepositories, layer) { + layers = append(layers, layer) + } + } + return layers +} diff --git a/internal/webhook/github/provider.go b/internal/webhook/github/provider.go index 82194857..a01cb1ca 100644 --- a/internal/webhook/github/provider.go +++ b/internal/webhook/github/provider.go @@ -99,6 +99,7 @@ func (g *Github) GetEvent(r *http.Request) (event.Event, error) { Revision: event.ParseRevision(payload.PullRequest.Base.Ref), Action: payload.Action, Changes: changedFiles, + Base: payload.PullRequest.Base.Ref, } default: return nil, errors.New("unsupported Event") diff --git a/internal/webhook/gitlab/provider.go b/internal/webhook/gitlab/provider.go index de68ba50..e018b7cb 100644 --- a/internal/webhook/gitlab/provider.go +++ b/internal/webhook/gitlab/provider.go @@ -76,6 +76,7 @@ func (g *Gitlab) GetEvent(r *http.Request) (event.Event, error) { Revision: event.ParseRevision(payload.ObjectAttributes.Ref), Action: getNormalizedAction(payload.ObjectAttributes.Action), Changes: changedFiles, + Base: payload.ObjectAttributes.TargetBranch, } default: return nil, errors.New("unsupported event") diff --git a/internal/webhook/webhook.go b/internal/webhook/webhook.go index c70e61f1..68f88a94 100644 --- a/internal/webhook/webhook.go +++ b/internal/webhook/webhook.go @@ -8,7 +8,6 @@ import ( log "github.com/sirupsen/logrus" - "github.com/padok-team/burrito/internal/annotations" "github.com/padok-team/burrito/internal/burrito/config" "github.com/padok-team/burrito/internal/webhook/event" "github.com/padok-team/burrito/internal/webhook/github" @@ -69,75 +68,49 @@ func (w *Webhook) Init() error { func (w *Webhook) GetHttpHandler() func(http.ResponseWriter, *http.Request) { log.Infof("webhook event received...") return func(writer http.ResponseWriter, r *http.Request) { - var payload interface{} var err error var event event.Event for _, p := range w.providers { if p.IsFromProvider(r) { - event, err := p.GetEvent(r) - if err != nil { - log.Errorf("could not get event: %s", err) - return - } + event, err = p.GetEvent(r) + break } } - if err != nil { log.Errorf("webhook processing failed: %s", err) status := http.StatusBadRequest if r.Method != "POST" { status = http.StatusMethodNotAllowed } - http.Error(writer, fmt.Sprintf("Webhook processing failed: %s", html.EscapeString(err.Error())), status) + http.Error(writer, fmt.Sprintf("webhook processing failed: %s", html.EscapeString(err.Error())), status) return } + if event == nil { + log.Infof("ignoring unknown webhook event") + http.Error(writer, "Unknown webhook event", http.StatusBadRequest) + } - w.Handle(payload) + err = w.Handle(event) + if err != nil { + log.Errorf("webhook processing worked but errored during event handling") + } } } -func (w *Webhook) getAffectedLayers(event event.Event) []*configv1alpha1.TerraformLayer { +func (w *Webhook) Handle(e event.Event) error { repositories := &configv1alpha1.TerraformRepositoryList{} err := w.Client.List(context.TODO(), repositories) if err != nil { - log.Errorf("could not get terraform repositories: %s", err) + return err } layers := &configv1alpha1.TerraformLayerList{} err = w.Client.List(context.TODO(), layers, &client.ListOptions{}) if err != nil { - log.Errorf("could not get terraform layers: %s", err) - } - affectedRepositories := []*configv1alpha1.TerraformRepository{} - for _, repo := range repositories.Items { - log.Infof("evaluating terraform repository %s for url %s", repo.Name, url) - if repo.Spec.Repository.Url != url { - log.Infof("evaluating terraform repository %s url %s not matching %s", repo.Name, repo.Spec.Repository.Url, url) - continue - } - } - affectedLayers := []*configv1alpha1.TerraformLayer{} - for _, layer := range layers.Items { - log.Printf("evaluating terraform layer %s for revision %s", layer.Name, revision) - if layer.Spec.Branch != revision && { - log.Infof("branch %s for terraform layer %s not matching revision %s", layer.Spec.Branch, layer.Name, revision) - continue - } - } - } - -} - -func (w *Webhook) Handle(payload interface{}) { - webUrls, sshUrls, revision, change, touchedHead, changedFiles := affectedRevisionInfo(payload) - allUrls := append(webUrls, sshUrls...) - - if len(allUrls) == 0 { - log.Infof("ignoring webhook event") - return + return err } - for _, url := range allUrls { - log.Infof("received event repo: %s, revision: %s, touchedHead: %v", url, revision, touchedHead) + err = e.Handle(w.Client, repositories.Items, layers.Items) + if err != nil { + return err } - - return + return nil }