diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index 0a17cceed..faa55f688 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -50,6 +50,7 @@ import ( "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/oam/util" "github.com/oam-dev/kubevela/pkg/utils" + "github.com/oam-dev/kubevela/pkg/utils/apply" "github.com/oam-dev/kubevela/pkg/utils/common" ) @@ -746,24 +747,26 @@ func Convert2SecName(name string) string { type Handler struct { ctx context.Context addon *types.Addon - clt client.Client + cli client.Client + apply apply.Applicator source *GitAddonSource args map[string]interface{} } -func newAddonHandler(ctx context.Context, addon *types.Addon, clt client.Client, source *GitAddonSource, args map[string]interface{}) Handler { +func newAddonHandler(ctx context.Context, addon *types.Addon, cli client.Client, apply apply.Applicator, source *GitAddonSource, args map[string]interface{}) Handler { return Handler{ ctx: ctx, addon: addon, - clt: clt, + cli: cli, + apply: apply, source: source, args: args, } } // EnableAddon will enable addon with dependency check, source is where addon from. -func EnableAddon(ctx context.Context, addon *types.Addon, clt client.Client, source *GitAddonSource, args map[string]interface{}) error { - h := newAddonHandler(ctx, addon, clt, source, args) +func EnableAddon(ctx context.Context, addon *types.Addon, cli client.Client, apply apply.Applicator, source *GitAddonSource, args map[string]interface{}) error { + h := newAddonHandler(ctx, addon, cli, apply, source, args) err := h.enableAddon() if err != nil { return err @@ -786,7 +789,7 @@ func (h *Handler) enableAddon() error { func (h *Handler) checkDependencies() error { var app v1beta1.Application for _, dep := range h.addon.Dependencies { - err := h.clt.Get(h.ctx, client.ObjectKey{ + err := h.cli.Get(h.ctx, client.ObjectKey{ Namespace: types.DefaultKubeVelaNS, Name: Convert2AppName(dep.Name), }, &app) @@ -821,19 +824,14 @@ func (h *Handler) dispatchAddonResource() error { return errors.Wrap(err, "render addon application fail") } - err = h.clt.Get(h.ctx, client.ObjectKeyFromObject(app), app) - if err == nil { - return errors.New("addon is already enabled") - } - - err = h.clt.Create(h.ctx, app) + err = h.apply.Apply(h.ctx, app) if err != nil { return errors.Wrap(err, "fail to create application") } for _, def := range defs { addOwner(def, app) - err = h.clt.Create(h.ctx, def) + err = h.apply.Apply(h.ctx, def) if err != nil { return err } @@ -841,7 +839,7 @@ func (h *Handler) dispatchAddonResource() error { for _, schema := range schemas { addOwner(schema, app) - err = h.clt.Create(h.ctx, schema) + err = h.apply.Apply(h.ctx, schema) if err != nil { return err } @@ -850,7 +848,7 @@ func (h *Handler) dispatchAddonResource() error { if h.args != nil && len(h.args) > 0 { sec := RenderArgsSecret(h.addon, h.args) addOwner(sec, app) - err = h.clt.Create(h.ctx, sec) + err = h.apply.Apply(h.ctx, sec) if err != nil { return err } diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go index c821b0de9..abf065455 100644 --- a/pkg/apiserver/rest/apis/v1/types.go +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -91,6 +91,11 @@ type ListAddonResponse struct { Addons []*types.AddonMeta `json:"addons"` } +// ListEnabledAddonResponse defines the format for enabled addon list response +type ListEnabledAddonResponse struct { + EnabledAddons []*AddonStatusResponse +} + // DetailAddonResponse defines the format for showing the addon details type DetailAddonResponse struct { types.AddonMeta @@ -113,6 +118,7 @@ type AddonDefinition struct { // AddonStatusResponse defines the format of addon status response type AddonStatusResponse struct { + Name string `json:"name"` Phase AddonPhase `json:"phase"` Args map[string]string `json:"args"` diff --git a/pkg/apiserver/rest/usecase/addon.go b/pkg/apiserver/rest/usecase/addon.go index 89b913b48..5fa714071 100644 --- a/pkg/apiserver/rest/usecase/addon.go +++ b/pkg/apiserver/rest/usecase/addon.go @@ -42,6 +42,7 @@ import ( apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" restutils "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils" "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode" + "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/utils/apply" ) @@ -57,6 +58,8 @@ type AddonUsecase interface { GetAddon(ctx context.Context, name string, registry string) (*apis.DetailAddonResponse, error) EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error DisableAddon(ctx context.Context, name string) error + ListEnabledAddon(ctx context.Context) ([]*apis.AddonStatusResponse, error) + UpdateAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error } // AddonImpl2AddonRes convert types.Addon to the type apiserver need @@ -165,31 +168,25 @@ func (u *addonUsecaseImpl) StatusAddon(ctx context.Context, name string) (*apis. return nil, bcode.ErrGetAddonApplication } - switch app.Status.Phase { - case common2.ApplicationRunning: - res := apis.AddonStatusResponse{ - Phase: apis.AddonPhaseEnabled, - EnablingProgress: nil, - } - var sec v1.Secret - err := u.kubeClient.Get(ctx, client.ObjectKey{ - Namespace: types.DefaultKubeVelaNS, - Name: pkgaddon.Convert2SecName(name), - }, &sec) - if err != nil { - return nil, bcode.ErrAddonSecretGet - } - res.Args = make(map[string]string, len(sec.Data)) - for k, v := range sec.Data { - res.Args[k] = string(v) - } - return &res, nil - default: - return &apis.AddonStatusResponse{ - Phase: apis.AddonPhaseEnabling, - EnablingProgress: nil, - }, nil + res := apis.AddonStatusResponse{ + Phase: convertAppStateToAddonPhase(app.Status.Phase), + EnablingProgress: nil, } + var sec v1.Secret + err = u.kubeClient.Get(ctx, client.ObjectKey{ + Namespace: types.DefaultKubeVelaNS, + Name: pkgaddon.Convert2SecName(name), + }, &sec) + if err != nil { + return nil, bcode.ErrAddonSecretGet + } + + res.Args = make(map[string]string, len(sec.Data)) + for k, v := range sec.Data { + res.Args[k] = string(v) + } + + return &res, nil } func (u *addonUsecaseImpl) ListAddons(ctx context.Context, registry, query string) ([]*apis.DetailAddonResponse, error) { @@ -360,7 +357,7 @@ func (u *addonUsecaseImpl) EnableAddon(ctx context.Context, name string, args ap continue } - err = pkgaddon.EnableAddon(ctx, addon, u.kubeClient, r.Git, args.Args) + err = pkgaddon.EnableAddon(ctx, addon, u.kubeClient, u.apply, r.Git, args.Args) if err != nil { log.Logger.Errorf("err when enable addon: %v", err) return bcode.ErrAddonApply @@ -402,6 +399,66 @@ func (u *addonUsecaseImpl) DisableAddon(ctx context.Context, name string) error return nil } +func (u *addonUsecaseImpl) ListEnabledAddon(ctx context.Context) ([]*apis.AddonStatusResponse, error) { + apps := &v1beta1.ApplicationList{} + if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil { + return nil, err + } + var response []*apis.AddonStatusResponse + for _, application := range apps.Items { + if addonName := application.Labels[oam.LabelAddonName]; addonName != "" { + if application.Status.Phase != common2.ApplicationRunning { + continue + } + response = append(response, &apis.AddonStatusResponse{ + Name: addonName, + Phase: convertAppStateToAddonPhase(application.Status.Phase), + }) + } + } + return response, nil +} + +func (u *addonUsecaseImpl) UpdateAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error { + + var app v1beta1.Application + // check addon application whether exist + err := u.kubeClient.Get(context.Background(), client.ObjectKey{ + Namespace: types.DefaultKubeVelaNS, + Name: pkgaddon.Convert2AppName(name), + }, &app) + if err != nil { + return err + } + + var addon *types.Addon + registries, err := u.ListAddonRegistries(ctx) + if err != nil { + return err + } + + for _, r := range registries { + var exist bool + if addon, exist = u.tryGetAddonFromCache(r.Name, name); !exist { + addon, err = pkgaddon.GetAddon(name, r.Git, pkgaddon.EnableLevelOptions) + } + if err != nil && !errors.Is(err, pkgaddon.ErrNotExist) { + return bcode.WrapGithubRateLimitErr(err) + } + if addon == nil { + continue + } + + err = pkgaddon.EnableAddon(ctx, addon, u.kubeClient, u.apply, r.Git, args.Args) + if err != nil { + log.Logger.Errorf("err when enable addon: %v", err) + return bcode.ErrAddonApply + } + return nil + } + return bcode.ErrAddonNotExist +} + func addonRegistryModelFromCreateAddonRegistryRequest(req apis.CreateAddonRegistryRequest) *model.AddonRegistry { return &model.AddonRegistry{ Name: req.Name, @@ -435,3 +492,12 @@ func ConvertAddonRegistryModel2AddonRegistryMeta(r *model.AddonRegistry) *apis.A Git: r.Git, } } + +func convertAppStateToAddonPhase(state common2.ApplicationPhase) apis.AddonPhase { + switch state { + case common2.ApplicationRunning: + return apis.AddonPhaseEnabled + default: + return apis.AddonPhaseEnabling + } +} diff --git a/pkg/apiserver/rest/webservice/addon.go b/pkg/apiserver/rest/webservice/addon.go index 333b6ed7b..04efbb696 100644 --- a/pkg/apiserver/rest/webservice/addon.go +++ b/pkg/apiserver/rest/webservice/addon.go @@ -34,6 +34,13 @@ func NewAddonWebService(u usecase.AddonUsecase) WebService { } } +// NewEnabledAddonWebService returns enabled addon web service +func NewEnabledAddonWebService(u usecase.AddonUsecase) WebService { + return &enabledAddonWebService{ + addonUsecase: u, + } +} + type addonWebService struct { addonUsecase usecase.AddonUsecase } @@ -96,6 +103,16 @@ func (s *addonWebService) GetWebService() *restful.WebService { Param(ws.PathParameter("name", "addon name to enable").DataType("string").Required(true)). Writes(apis.AddonStatusResponse{})) + // update addon + ws.Route(ws.PUT("/{name}/update").To(s.updateAddon). + Doc("update an addon"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.EnableAddonRequest{}). + Returns(200, "", apis.AddonStatusResponse{}). + Returns(400, "", bcode.Bcode{}). + Param(ws.PathParameter("name", "addon name to update").DataType("string").Required(true)). + Writes(apis.AddonStatusResponse{})) + return ws } @@ -185,3 +202,69 @@ func (s *addonWebService) statusAddon(req *restful.Request, res *restful.Respons return } } + +func (s *addonWebService) updateAddon(req *restful.Request, res *restful.Response) { + var createReq apis.EnableAddonRequest + var args []byte + _, err := req.Request.Body.Read(args) + if err == nil { + err := req.ReadEntity(&createReq) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err = validate.Struct(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + } + + name := req.PathParameter("name") + err = s.addonUsecase.UpdateAddon(req.Request.Context(), name, createReq) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + + s.statusAddon(req, res) +} + +type enabledAddonWebService struct { + addonUsecase usecase.AddonUsecase +} + +func (s *enabledAddonWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/enabled-addon"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for addon management") + + tags := []string{"addon"} + + // List enabled addon from cluster + ws.Route(ws.GET("/").To(s.list). + Doc("list all addons"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")). + Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")). + Returns(200, "", apis.ListAddonResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.ListAddonResponse{})) + + return ws +} + +func (s *enabledAddonWebService) list(req *restful.Request, res *restful.Response) { + enabledAddons, err := s.addonUsecase.ListEnabledAddon(req.Request.Context()) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + + err = res.WriteEntity(apis.ListEnabledAddonResponse{EnabledAddons: enabledAddons}) + if err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/rest/webservice/webservice.go b/pkg/apiserver/rest/webservice/webservice.go index 2c1b59ec4..84c31a3f5 100644 --- a/pkg/apiserver/rest/webservice/webservice.go +++ b/pkg/apiserver/rest/webservice/webservice.go @@ -73,6 +73,7 @@ func Init(ds datastore.DataStore) { RegistWebService(NewNamespaceWebService(namespaceUsecase)) RegistWebService(NewDefinitionWebservice(definitionUsecase)) RegistWebService(NewAddonWebService(addonUsecase)) + RegistWebService(NewEnabledAddonWebService(addonUsecase)) RegistWebService(NewAddonRegistryWebService(addonUsecase)) RegistWebService(NewOAMApplication(oamApplicationUsecase)) RegistWebService(&policyDefinitionWebservice{})