diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go index 4f25625ed..3da58c466 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go @@ -29,30 +29,33 @@ const ControllerRevisionComponentLabel = "controller.oam.dev/component" // ComponentHandler will watch component change and generate Revision automatically. type ComponentHandler struct { - Client client.Client - Logger logging.Logger - RevisionLimit int + Client client.Client + Logger logging.Logger + RevisionLimit int + CustomWebHookURL string } // Create implements EventHandler func (c *ComponentHandler) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { - if !c.createControllerRevision(evt.Meta, evt.Object) { + reqs, succeed := c.createControllerRevision(evt.Meta, evt.Object) + if !succeed { // No revision created, return return } - for _, req := range c.getRelatedAppConfig(evt.Meta) { + for _, req := range reqs { q.Add(req) } } // Update implements EventHandler func (c *ComponentHandler) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { - if !c.createControllerRevision(evt.MetaNew, evt.ObjectNew) { + reqs, succeed := c.createControllerRevision(evt.MetaNew, evt.ObjectNew) + if !succeed { // No revision created, return return } // Note(wonderflow): MetaOld => MetaNew, requeue once is enough - for _, req := range c.getRelatedAppConfig(evt.MetaNew) { + for _, req := range reqs { q.Add(req) } } @@ -133,14 +136,22 @@ func newTrue() *bool { return &b } -func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtime.Object) bool { +func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtime.Object) ([]reconcile.Request, bool) { curComp := obj.(*v1alpha2.Component) comp := curComp.DeepCopy() diff, curRevision := c.IsRevisionDiff(mt, comp) if !diff { // No difference, no need to create new revision. - return false + return nil, false } + + reqs := c.getRelatedAppConfig(mt) + // Hook to custom revision service if exist + if err := c.customComponentRevisionHook(reqs, comp); err != nil { + c.Logger.Info(fmt.Sprintf("fail to hook from custom revision service(%s) %v", c.CustomWebHookURL, err), "componentName", mt.GetName()) + return nil, false + } + nextRevision := curRevision + 1 revisionName := ConstructRevisionName(mt.GetName(), nextRevision) @@ -177,13 +188,13 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim err := c.Client.Create(context.TODO(), &revision) if err != nil { c.Logger.Info(fmt.Sprintf("error create controllerRevision %v", err), "componentName", mt.GetName()) - return false + return nil, false } err = c.Client.Status().Update(context.Background(), comp) if err != nil { c.Logger.Info(fmt.Sprintf("update component status latestRevision %s err %v", revisionName, err), "componentName", mt.GetName()) - return false + return nil, false } c.Logger.Info(fmt.Sprintf("ControllerRevision %s created", revisionName)) if int64(c.RevisionLimit) < nextRevision { @@ -191,7 +202,7 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim c.Logger.Info(fmt.Sprintf("failed to clean up revisions of Component %v.", err)) } } - return true + return reqs, true } // get sorted controllerRevisions, prepare to delete controllerRevisions diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go new file mode 100644 index 000000000..3765578b2 --- /dev/null +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go @@ -0,0 +1,66 @@ +/* +Copyright 2020 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package applicationconfiguration + +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/pkg/server/util" +) + +// RevisionHookRequest is request body for custom component revision hook +type RevisionHookRequest struct { + RelatedApps []reconcile.Request `json:"relatedApps"` + Comp *v1alpha2.Component `json:"component"` +} + +func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.Request, comp *v1alpha2.Component) error { + if c.CustomWebHookURL == "" { + return nil + } + req := RevisionHookRequest{ + RelatedApps: relatedApps, + Comp: comp.DeepCopy(), + } + data, err := json.Marshal(req) + if err != nil { + return err + } + httpRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.CustomWebHookURL, bytes.NewBuffer(data)) + if err != nil { + return err + } + httpRequest.Header.Set("Content-Type", util.ContentTypeJSON) + resp, err := http.DefaultClient.Do(httpRequest) + if err != nil { + return err + } + //nolint:errcheck + defer resp.Body.Close() + respData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return json.Unmarshal(respData, comp) +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/revision_enable_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/revision_enable_test.go index f908d16d3..d2b8c584e 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/revision_enable_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/revision_enable_test.go @@ -137,7 +137,10 @@ var _ = Describe("Test ApplicationConfiguration Component Revision Enabled trait Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV1)).Should(Succeed()) By("component handler will automatically create controller revision") - Expect(componentHandler.createControllerRevision(cmpV1, cmpV1)).Should(BeTrue()) + Expect(func() bool { + _, ok := componentHandler.createControllerRevision(cmpV1, cmpV1) + return ok + }()).Should(BeTrue()) var crList v1.ControllerRevisionList By("Check controller revision created successfully") Eventually(func() error { @@ -250,7 +253,7 @@ var _ = Describe("Test ApplicationConfiguration Component Revision Enabled trait By("Update Component") Expect(k8sClient.Update(ctx, cmpV2)).Should(Succeed()) By("component handler will automatically create a ne controller revision") - Expect(componentHandler.createControllerRevision(cmpV2, cmpV2)).Should(BeTrue()) + Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV2, cmpV2); return ok }()).Should(BeTrue()) By("Check controller revision created successfully") Eventually(func() error { labels := &metav1.LabelSelector{