Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add the plugin management API #747

Merged
merged 9 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ dist
.pnp.*

tsconfig.tsbuildinfo

e2e-plugins
138 changes: 106 additions & 32 deletions e2e-test/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package e2e_test

import (
"cuelang.org/go/pkg/strings"
"github.com/google/go-cmp/cmp"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
Expand All @@ -28,30 +27,116 @@ import (
)

var _ = Describe("Test the plugin rest api", func() {
It("Test list installed plugins", func() {
defer GinkgoRecover()
res := get("/plugins")
var lpr apisv1.ListPluginResponse
Expect(decodeResponseBody(res, &lpr)).Should(Succeed())
Expect(cmp.Diff(len(lpr.Plugins), 2)).Should(BeEmpty())
Context("Test manage plugin API", func() {
By("Request to /manage/plugins")

It("Test list installed plugins", func() {
defer GinkgoRecover()
res := get("/manage/plugins")
var lpr apisv1.ListManagedPluginResponse
Expect(decodeResponseBody(res, &lpr)).Should(Succeed())
Expect(len(lpr.Plugins)).Should(Equal(2))
Expect(lpr.Plugins[0].Enabled).Should(BeFalse())
})

It("Test detail a installed plugin", func() {
defer GinkgoRecover()
res := get("/manage/plugins/app-demo")
var dto apisv1.ManagedPluginDTO
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Module).Should(Equal("plugins/app-demo/module"))
})

It("Test enable/set/disable plugin", func() {
By("before enable")
res := get("/manage/plugins/app-demo")
var dto apisv1.ManagedPluginDTO
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Enabled).Should(BeFalse())

By("enable")
enableReq := apisv1.PluginEnableRequest{
JSONData: map[string]interface{}{
"arg1": "value1",
},
SecureJSONData: map[string]interface{}{
"arg2": "value2",
},
}
res = post("/manage/plugins/app-demo/enable", enableReq)
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Enabled).Should(BeTrue())
Expect(dto.JSONSetting["arg1"]).Should(Equal("value1"))
Expect(dto.SecureJSONFields["arg2"]).Should(Equal(true))

By("after enable, get")
res = get("/manage/plugins/app-demo")
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Enabled).Should(BeTrue())
Expect(dto.JSONSetting["arg1"]).Should(Equal("value1"))
Expect(dto.SecureJSONFields["arg2"]).Should(Equal(true))

By("change the setting")
setReq := apisv1.PluginSetRequest{
JSONData: map[string]interface{}{
"arg1": "changedValue1",
},
SecureJSONData: map[string]interface{}{
"arg2": "changedValue2",
"addArg": "addValue",
},
}
res = post("/manage/plugins/app-demo/setting", setReq)
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Enabled).Should(BeTrue())
Expect(dto.JSONSetting["arg1"]).Should(Equal("changedValue1"))
Expect(dto.SecureJSONFields["arg2"]).Should(Equal(true))
Expect(dto.SecureJSONFields["addArg"]).Should(Equal(true))

By("disable the plugin")
res = post("/manage/plugins/app-demo/disable", nil)
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.Enabled).Should(BeFalse())
})
})

It("Test get a installed plugin", func() {
defer GinkgoRecover()
res := get("/plugins/app-demo")
var dto apisv1.PluginDTO
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(cmp.Diff(dto.Module, "plugins/app-demo/module")).Should(BeEmpty())
Context("Test plugin API", func() {
By("Request to /proxy/plugins")

It("Test to request the kube API", func() {
defer GinkgoRecover()
By("Before enable the plugin, request the plugin API should return 400")

res := get(baseDomain + "/proxy/plugins/node-dashboard/api/v1/nodes")
var nodeList corev1.NodeList
err := decodeResponseBody(res, &nodeList)
Expect(err).Should(HaveOccurred())
Expect(strings.Contains(err.Error(), "the plugin is not enabled")).Should(BeTrue())

By("After enable the plugin, request the plugin API should return 200")

res = post("/manage/plugins/node-dashboard/enable", nil)
var dto apisv1.PluginDTO
Expect(decodeResponseBody(res, &dto)).Should(Succeed())
Expect(dto.ID).Should(Equal("node-dashboard"))

res = get(baseDomain + "/proxy/plugins/node-dashboard/api/v1/nodes")
Expect(decodeResponseBody(res, &nodeList)).Should(Succeed())
Expect(len(nodeList.Items)).Should(Equal(1))
})
})
})

var _ = Describe("Test to request the plugin static files", func() {
It("Test to get the module file", func() {
defer GinkgoRecover()
res := get(baseDomain + "/public/plugins/app-demo/module.js")
defer func() { _ = res.Body.Close() }()
Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty())
Context("Test to request the plugin static files", func() {
By("Request to /public/plugins")

It("Test to get the module file", func() {
defer GinkgoRecover()
res := get(baseDomain + "/public/plugins/app-demo/module.js")
defer func() { _ = res.Body.Close() }()
Expect(res.StatusCode).Should(Equal(200))
})
})

})

var _ = Describe("Test to request the dex plugin", func() {
Expand All @@ -61,17 +146,6 @@ var _ = Describe("Test to request the dex plugin", func() {
var bcode bcode.Bcode
err := decodeResponseBody(res, &bcode)
Expect(strings.HasPrefix(err.Error(), "response code is not 200")).Should(BeTrue())
Expect(cmp.Diff(bcode.BusinessCode, int32(404))).Should(BeEmpty())
})
})

var _ = Describe("Test to request the kube API", func() {
It("Test to request the dex", func() {
defer GinkgoRecover()
res := get(baseDomain + "/proxy/plugins/node-dashboard/api/v1/nodes")
var nodeList corev1.NodeList
err := decodeResponseBody(res, &nodeList)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(nodeList.Items), 1)).Should(BeEmpty())
Expect(bcode.BusinessCode).Should(Equal(int32(404)))
})
})
1 change: 1 addition & 0 deletions e2e-test/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var _ = BeforeSuite(func() {
server := server.New(cfg)
Expect(server).ShouldNot(BeNil())
go func() {
defer GinkgoRecover()
err := server.Run(ctx, make(chan error))
Expect(err).ShouldNot(HaveOccurred())
}()
Expand Down
4 changes: 4 additions & 0 deletions pkg/plugin/proxy/kube_service_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ func (k *kubeServiceProxy) Handler(req *http.Request, res http.ResponseWriter) {
if err := k.kubeClient.Get(req.Context(), apitypes.NamespacedName{Namespace: namespace, Name: name}, &service); err != nil {
klog.Errorf("failed to discover the backend service:%s/%s err:%s ", name, namespace, err.Error())
bcode.ReturnHTTPError(req, res, bcode.ErrNotFound)
return
}
if len(service.Spec.Ports) == 0 {
klog.Errorf("there is no port in the backend service:%s/%s err:%s ", name, namespace)
bcode.ReturnHTTPError(req, res, bcode.ErrNotFound)
return
}
matchPort := service.Spec.Ports[0].Port
if k.plugin.BackendService.Port != 0 {
Expand All @@ -79,11 +81,13 @@ func (k *kubeServiceProxy) Handler(req *http.Request, res http.ResponseWriter) {
if !havePort {
klog.Errorf("there is no port same with the configured port in the backend service:%s/%s err:%s ", name, namespace)
bcode.ReturnHTTPError(req, res, bcode.ErrNotFound)
return
}
}
if service.Spec.ClusterIP == "" {
klog.Errorf("there is no port same with the configured port in the backend service:%s/%s err:%s ", name, namespace)
bcode.ReturnHTTPError(req, res, bcode.ErrUpstreamNotFound)
return
}
availableEndpoint, err := url.Parse(fmt.Sprintf("http://%s:%d", service.Spec.ClusterIP, matchPort))
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/plugin/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ var _ = Describe("Test proxy", func() {
},
},
}
pluginService := service.NewTestPluginService(config.PluginConfig{}, k8sClient)
pluginService := service.NewTestPluginService(config.PluginConfig{}, k8sClient, nil)
Expect(pluginService.InitPluginRole(context.TODO(), plugin)).To(BeNil())

proxy, err := NewBackendPluginProxy(plugin, k8sClient, cfg)
Expand Down
10 changes: 4 additions & 6 deletions pkg/plugin/types/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package types

import (
rbacv1 "k8s.io/api/rbac/v1"

"github.com/kubevela/velaux/pkg/server/domain/model"
)

// Plugin VelaUX plugin model
Expand All @@ -31,8 +29,6 @@ type Plugin struct {
// SystemJS fields
Module string
BaseURL string
// Plugin Settings
Setting model.PluginSetting
}

// BuildInfo the plugin build info
Expand Down Expand Up @@ -80,8 +76,10 @@ type Requirement struct {

// JSONData represents the plugin's plugin.json
type JSONData struct {
ID string `json:"id"`
Type Type `json:"type"`
ID string `json:"id"`
Type Type `json:"type"`
// there are four sub types in the definition plugin type, includes: component, trait, policy ,and workflow-step.
SubType string `json:"subType"`
Name string `json:"name"`
Info Info `json:"info"`
Includes []*Includes `json:"includes"`
Expand Down
10 changes: 10 additions & 0 deletions pkg/server/domain/model/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func init() {
type PluginSetting struct {
BaseModel
ID string `json:"id"`
Enabled bool `json:"enabled"`
JSONData map[string]interface{} `json:"jsonData"`
SecureJSONData map[string]interface{} `json:"secureJsonData"`
}
Expand All @@ -42,3 +43,12 @@ func (p PluginSetting) TableName() string {
func (p PluginSetting) ShortTableName() string {
return "plugin"
}

// Index return custom index
func (p PluginSetting) Index() map[string]interface{} {
index := make(map[string]interface{})
if p.ID != "" {
index["id"] = p.ID
}
return index
}
Loading