diff --git a/Dockerfile b/Dockerfile index 0882cc6cb..8a5318e68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +ARG BASE_IMAGE="alpine:latest" # Build the manager binary FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.16-alpine as builder @@ -24,15 +25,10 @@ RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \ go build -a -ldflags "-s -w -X github.com/oam-dev/kubevela/version.VelaVersion=${VERSION:-undefined} -X github.com/oam-dev/kubevela/version.GitRevision=${GITVERSION:-undefined}" \ -o manager-${TARGETARCH} main.go -RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \ - go build -a -ldflags "-s -w -X github.com/oam-dev/kubevela/version.VelaVersion=${VERSION:-undefined} -X github.com/oam-dev/kubevela/version.GitRevision=${GITVERSION:-undefined}" \ - -o apiserver-${TARGETARCH} cmd/apiserver/main.go - # Use alpine as base image due to the discussion in issue #1448 # You can replace distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details # Overwrite `BASE_IMAGE` by passing `--build-arg=BASE_IMAGE=gcr.io/distroless/static:nonroot` -ARG BASE_IMAGE FROM ${BASE_IMAGE:-alpine:latest} # This is required by daemon connnecting with cri RUN apk add --no-cache ca-certificates bash @@ -41,7 +37,6 @@ WORKDIR / ARG TARGETARCH COPY --from=builder /workspace/manager-${TARGETARCH} /usr/local/bin/manager -COPY --from=builder /workspace/apiserver-${TARGETARCH} /usr/local/bin/apiserver COPY entrypoint.sh /usr/local/bin/ diff --git a/Dockerfile.apiserver b/Dockerfile.apiserver new file mode 100644 index 000000000..4b39b7130 --- /dev/null +++ b/Dockerfile.apiserver @@ -0,0 +1,48 @@ +ARG BASE_IMAGE="alpine:latest" +# Build the manager binary +FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.16-alpine as builder +ARG GOPROXY +ENV GOPROXY=${GOPROXY:-https://goproxy.cn} +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY cmd/core/main.go main.go +COPY cmd/apiserver/main.go cmd/apiserver/main.go +COPY apis/ apis/ +COPY pkg/ pkg/ +COPY version/ version/ + +# Build +ARG TARGETARCH +ARG VERSION +ARG GITVERSION + +RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \ + go build -a -ldflags "-s -w -X github.com/oam-dev/kubevela/version.VelaVersion=${VERSION:-undefined} -X github.com/oam-dev/kubevela/version.GitRevision=${GITVERSION:-undefined}" \ + -o apiserver-${TARGETARCH} cmd/apiserver/main.go + +# Use alpine as base image due to the discussion in issue #1448 +# You can replace distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +# Overwrite `BASE_IMAGE` by passing `--build-arg=BASE_IMAGE=gcr.io/distroless/static:nonroot` + +FROM ${BASE_IMAGE:-alpine:latest} +# This is required by daemon connnecting with cri +RUN apk add --no-cache ca-certificates bash + +WORKDIR / + +ARG TARGETARCH +COPY --from=builder /workspace/apiserver-${TARGETARCH} /usr/local/bin/apiserver + +COPY entrypoint.sh /usr/local/bin/ + +ENTRYPOINT ["entrypoint.sh"] + +CMD ["apiserver"] diff --git a/Makefile b/Makefile index 85d8f3fe4..d21c62896 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ endif # Image URL to use all building/pushing image targets VELA_CORE_IMAGE ?= vela-core:latest VELA_CORE_TEST_IMAGE ?= vela-core-test:$(GIT_COMMIT) +VELA_APISERVER_IMAGE ?= apiserver:latest VELA_RUNTIME_ROLLOUT_IMAGE ?= vela-runtime-rollout:latest VELA_RUNTIME_ROLLOUT_TEST_IMAGE ?= vela-runtime-rollout-test:$(GIT_COMMIT) RUNTIME_CLUSTER_CONFIG ?= /tmp/worker.kubeconfig @@ -133,8 +134,12 @@ check-diff: reviewable @$(OK) branch is clean # Build the docker image -docker-build: +docker-build: docker-build-core docker-build-apiserver + @$(OK) +docker-build-core: docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_CORE_IMAGE) . +docker-build-apiserver: + docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_APISERVER_IMAGE) -f Dockerfile.apiserver . # Build the runtime docker image docker-build-runtime-rollout: diff --git a/pkg/apiserver/model/application.go b/pkg/apiserver/model/application.go index 7ff36c523..9a3f1be8b 100644 --- a/pkg/apiserver/model/application.go +++ b/pkg/apiserver/model/application.go @@ -30,6 +30,7 @@ func init() { type Application struct { Model Name string `json:"name"` + Alias string `json:"alias"` Namespace string `json:"namespace"` Description string `json:"description"` Icon string `json:"icon"` @@ -82,6 +83,7 @@ type ApplicationComponent struct { Icon string `json:"icon,omitempty"` Creator string `json:"creator"` Name string `json:"name"` + Alias string `json:"alias"` Type string `json:"type"` // ExternalRevision specified the component revisionName diff --git a/pkg/apiserver/model/cluster.go b/pkg/apiserver/model/cluster.go index 34517de99..553f6b3cf 100644 --- a/pkg/apiserver/model/cluster.go +++ b/pkg/apiserver/model/cluster.go @@ -22,10 +22,11 @@ func init() { // ProviderInfo describes the information from provider API type ProviderInfo struct { - Name string `json:"name"` - ID string `json:"id"` - Zone string `json:"zone"` - Labels map[string]string `json:"labels"` + Provider string `json:"provider"` + ClusterName string `json:"name"` + ID string `json:"id"` + Zone string `json:"zone"` + Labels map[string]string `json:"labels"` } const ( @@ -39,6 +40,7 @@ const ( type Cluster struct { Model Name string `json:"name"` + Alias string `json:"alias"` Description string `json:"description"` Icon string `json:"icon"` Labels map[string]string `json:"labels"` diff --git a/pkg/apiserver/model/workflow.go b/pkg/apiserver/model/workflow.go index eed20cca7..c0395f54d 100644 --- a/pkg/apiserver/model/workflow.go +++ b/pkg/apiserver/model/workflow.go @@ -32,6 +32,7 @@ func init() { type Workflow struct { Model Name string `json:"name"` + Alias string `json:"alias"` Description string `json:"description"` Enable bool `json:"enable"` // Workflow used by the default diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go index fa214a81e..bbfca1356 100644 --- a/pkg/apiserver/rest/apis/v1/types.go +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -50,21 +50,23 @@ type EmptyResponse struct{} // CreateAddonRegistryRequest defines the format for addon registry create request type CreateAddonRegistryRequest struct { - Name string `json:"name" validate:"required"` - - Git *model.GitAddonSource `json:"git,omitempty"` + Name string `json:"name" validate:"checkname"` + Git *model.GitAddonSource `json:"git,omitempty"` } // AddonRegistryMeta defines the format for a single addon registry type AddonRegistryMeta struct { - Name string `json:"name" validate:"required"` + Name string `json:"name" validate:"required"` + Git *model.GitAddonSource `json:"git,omitempty"` +} - Git *model.GitAddonSource `json:"git,omitempty"` +// ListAddonRegistryResponse list addon registry +type ListAddonRegistryResponse struct { + Registrys []*AddonRegistryMeta `json:"registrys"` } // EnableAddonRequest defines the format for enable addon request type EnableAddonRequest struct { - // Args is the key-value environment variables, e.g. AK/SK credentials. Args map[string]string `json:"args,omitempty"` } @@ -76,15 +78,11 @@ type ListAddonResponse struct { // AddonMeta defines the format for a single addon type AddonMeta struct { - Name string `json:"name"` - - Version string `json:"version"` - - Description string `json:"description"` - - Icon string `json:"icon"` - - Tags []string `json:"tags"` + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description"` + Icon string `json:"icon"` + Tags []string `json:"tags"` } // DetailAddonResponse defines the format for showing the addon details @@ -120,6 +118,7 @@ type AccessKeyRequest struct { // CreateClusterRequest request parameters to create a cluster type CreateClusterRequest struct { Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description,omitempty"` Icon string `json:"icon"` KubeConfig string `json:"kubeConfig,omitempty" validate:"required_without=KubeConfigSecret"` @@ -134,6 +133,7 @@ type ConnectCloudClusterRequest struct { AccessKeySecret string `json:"accessKeySecret"` ClusterID string `json:"clusterID"` Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description,omitempty"` Icon string `json:"icon"` Labels map[string]string `json:"labels,omitempty"` @@ -174,6 +174,7 @@ type ListCloudClusterResponse struct { // ClusterBase cluster base model type ClusterBase struct { Name string `json:"name"` + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description"` Icon string `json:"icon"` Labels map[string]string `json:"labels"` @@ -186,14 +187,35 @@ type ClusterBase struct { Reason string `json:"reason"` } +// ListApplicatioOptions list application query options +type ListApplicatioOptions struct { + Namespace string `json:"namespace"` + Cluster string `json:"cluster"` + Query string `json:"query"` +} + // ListApplicationResponse list applications by query params type ListApplicationResponse struct { Applications []*ApplicationBase `json:"applications"` } +// EnvBindList env bind list +type EnvBindList []*EnvBind + +// ContainCluster contain cluster name +func (e EnvBindList) ContainCluster(name string) bool { + for _, eb := range e { + if eb.ClusterSelector != nil && eb.ClusterSelector.Name == name { + return true + } + } + return false +} + // ApplicationBase application base model type ApplicationBase struct { Name string `json:"name"` + Alias string `json:"alias"` Namespace string `json:"namespace"` Description string `json:"description"` CreateTime time.Time `json:"createTime"` @@ -201,7 +223,7 @@ type ApplicationBase struct { Icon string `json:"icon"` Labels map[string]string `json:"labels,omitempty"` Status string `json:"status"` - EnvBind []*EnvBind `json:"envBind,omitempty"` + EnvBind EnvBindList `json:"envBind,omitempty"` GatewayRuleList []GatewayRule `json:"gatewayRule"` } @@ -227,6 +249,7 @@ type GatewayRule struct { // CreateApplicationRequest create application request body type CreateApplicationRequest struct { Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` Namespace string `json:"namespace" validate:"checkname"` Description string `json:"description"` Icon string `json:"icon"` @@ -276,6 +299,7 @@ type ApplicationResourceInfo struct { // ComponentBase component base model type ComponentBase struct { Name string `json:"name"` + Alias string `json:"alias"` Description string `json:"description"` Labels map[string]string `json:"labels,omitempty"` ComponentType string `json:"componentType"` @@ -296,6 +320,7 @@ type ComponentListResponse struct { // CreateComponentRequest create component request model type CreateComponentRequest struct { Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description"` Icon string `json:"icon"` Labels map[string]string `json:"labels,omitempty"` @@ -431,6 +456,7 @@ type PolicyDefinition struct { type CreateWorkflowRequest struct { AppName string `json:"appName" validate:"checkname"` Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description"` Steps []WorkflowStep `json:"steps,omitempty"` Enable bool `json:"enable"` @@ -439,6 +465,7 @@ type CreateWorkflowRequest struct { // UpdateWorkflowRequest update or create application workflow type UpdateWorkflowRequest struct { + Alias string `json:"alias" validate:"checkalias"` Description string `json:"description"` Steps []WorkflowStep `json:"steps,omitempty"` Enable bool `json:"enable"` @@ -471,6 +498,7 @@ type ListWorkflowResponse struct { // WorkflowBase workflow base model type WorkflowBase struct { Name string `json:"name"` + Alias string `json:"alias"` Description string `json:"description"` Enable bool `json:"enable"` Default bool `json:"default"` diff --git a/pkg/apiserver/rest/usecase/addon.go b/pkg/apiserver/rest/usecase/addon.go index 0b4e38dfa..44cb83f1e 100644 --- a/pkg/apiserver/rest/usecase/addon.go +++ b/pkg/apiserver/rest/usecase/addon.go @@ -5,27 +5,28 @@ import ( "context" "errors" "fmt" - "github.com/Masterminds/sprig" - "github.com/oam-dev/kubevela/pkg/apiserver/log" - "golang.org/x/oauth2" "net/http" "net/url" "path" "sort" "strings" "text/template" + "time" + "github.com/Masterminds/sprig" + "github.com/google/go-github/v32/github" + "golang.org/x/oauth2" errors2 "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/google/go-github/v32/github" common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/apiserver/clients" "github.com/oam-dev/kubevela/pkg/apiserver/datastore" + "github.com/oam-dev/kubevela/pkg/apiserver/log" "github.com/oam-dev/kubevela/pkg/apiserver/model" apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" restutils "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils" @@ -46,7 +47,8 @@ const ( type AddonUsecase interface { GetAddonRegistryModel(ctx context.Context, name string) (*model.AddonRegistry, error) CreateAddonRegistry(ctx context.Context, req apis.CreateAddonRegistryRequest) (*apis.AddonRegistryMeta, error) - ListAddons(ctx context.Context, detailed bool) ([]*apis.DetailAddonResponse, error) + ListAddonRegistries(ctx context.Context) ([]*apis.AddonRegistryMeta, error) + ListAddons(ctx context.Context, detailed bool, query string) ([]*apis.DetailAddonResponse, error) StatusAddon(name string) (*apis.AddonStatusResponse, error) GetAddon(ctx context.Context, name string) (*apis.DetailAddonResponse, error) EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error @@ -73,7 +75,7 @@ type addonUsecaseImpl struct { } func (u *addonUsecaseImpl) GetAddon(ctx context.Context, name string) (*apis.DetailAddonResponse, error) { - addons, err := u.ListAddons(ctx, true) + addons, err := u.ListAddons(ctx, true, "") if err != nil { return nil, err } @@ -121,7 +123,7 @@ func (u *addonUsecaseImpl) StatusAddon(name string) (*apis.AddonStatusResponse, } } -func (u *addonUsecaseImpl) ListAddons(ctx context.Context, detailed bool) ([]*apis.DetailAddonResponse, error) { +func (u *addonUsecaseImpl) ListAddons(ctx context.Context, detailed bool, query string) ([]*apis.DetailAddonResponse, error) { // Backward compatibility with ConfigMap addons. // We will deprecate ConfigMap and use Git based registry. addons, err := getAddonsFromConfigMap(detailed) @@ -129,17 +131,27 @@ func (u *addonUsecaseImpl) ListAddons(ctx context.Context, detailed bool) ([]*ap return nil, err } - rs, err := u.listAddonRegistries(ctx) + rs, err := u.ListAddonRegistries(ctx) if err != nil { return nil, err } for _, r := range rs { gitAddons, err := getAddonsFromGit(r.Git.URL, r.Git.Path, r.Git.Token, detailed) if err != nil { - return nil, err + log.Logger.Errorf("list addons from registry %s failure %s", r.Name, err.Error()) + continue } addons = mergeAddons(addons, gitAddons) } + if query != "" { + var new []*apis.DetailAddonResponse + for i, addon := range addons { + if strings.Contains(addon.Name, query) || strings.Contains(addon.Description, query) { + new = append(new, addons[i]) + } + } + addons = new + } sort.Slice(addons, func(i, j int) bool { return addons[i].Name < addons[j].Name }) @@ -175,7 +187,7 @@ func (u *addonUsecaseImpl) GetAddonRegistryModel(ctx context.Context, name strin return &r, nil } -func (u *addonUsecaseImpl) listAddonRegistries(ctx context.Context) ([]*apis.AddonRegistryMeta, error) { +func (u *addonUsecaseImpl) ListAddonRegistries(ctx context.Context) ([]*apis.AddonRegistryMeta, error) { var r = model.AddonRegistry{} entities, err := u.ds.List(ctx, &r, &datastore.ListOptions{}) if err != nil { @@ -307,6 +319,7 @@ func getAddonsFromGit(baseURL, dir, token string, detailed bool) ([]*apis.Detail ) tc = oauth2.NewClient(context.Background(), ts) } + tc.Timeout = time.Second * 10 clt := github.NewClient(tc) // TODO add error handling baseURL = strings.TrimSuffix(baseURL, ".git") @@ -329,7 +342,7 @@ func getAddonsFromGit(baseURL, dir, token string, detailed bool) ([]*apis.Detail } addonRes := apis.DetailAddonResponse{ AddonMeta: apis.AddonMeta{ - Name: *subItems.Name, + Name: converAddonName(*subItems.Name), }, } var err error @@ -384,7 +397,7 @@ func getAddonsFromConfigMap(detailed bool) ([]*apis.DetailAddonResponse, error) for _, addon := range cliAddons { d := &apis.DetailAddonResponse{ AddonMeta: apis.AddonMeta{ - Name: addon.Name, + Name: converAddonName(addon.Name), // TODO add actual Version, Icon, tags Version: "v1alpha1", Description: addon.Description, @@ -399,5 +412,8 @@ func getAddonsFromConfigMap(detailed bool) ([]*apis.DetailAddonResponse, error) addons = append(addons, d) } return addons, nil - +} + +func converAddonName(name string) string { + return strings.ReplaceAll(name, "/", "-") } diff --git a/pkg/apiserver/rest/usecase/application.go b/pkg/apiserver/rest/usecase/application.go index 279627196..c39fbe8d7 100644 --- a/pkg/apiserver/rest/usecase/application.go +++ b/pkg/apiserver/rest/usecase/application.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "strings" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -52,7 +53,7 @@ const ( // ApplicationUsecase application usecase type ApplicationUsecase interface { - ListApplications(ctx context.Context) ([]*apisv1.ApplicationBase, error) + ListApplications(ctx context.Context, listOptions apisv1.ListApplicatioOptions) ([]*apisv1.ApplicationBase, error) GetApplication(ctx context.Context, appName string) (*model.Application, error) DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error) PublishApplicationTemplate(ctx context.Context, app *model.Application) (*apisv1.ApplicationTemplateBase, error) @@ -92,15 +93,28 @@ func NewApplicationUsecase(ds datastore.DataStore, workflowUsecase WorkflowUseca } // ListApplications list applications -func (c *applicationUsecaseImpl) ListApplications(ctx context.Context) ([]*apisv1.ApplicationBase, error) { +func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptions apisv1.ListApplicatioOptions) ([]*apisv1.ApplicationBase, error) { var app = model.Application{} + if listOptions.Namespace != "" { + app.Namespace = listOptions.Namespace + } entitys, err := c.ds.List(ctx, &app, &datastore.ListOptions{}) if err != nil { return nil, err } var list []*apisv1.ApplicationBase for _, entity := range entitys { - list = append(list, c.converAppModelToBase(ctx, entity.(*model.Application))) + appBase := c.converAppModelToBase(ctx, entity.(*model.Application)) + if listOptions.Query != "" && + !(strings.Contains(appBase.Alias, listOptions.Query) || + strings.Contains(appBase.Name, listOptions.Query) || + strings.Contains(appBase.Description, listOptions.Query)) { + continue + } + if listOptions.Cluster != "" && !appBase.EnvBind.ContainCluster(listOptions.Cluster) { + continue + } + list = append(list, appBase) } return list, nil } @@ -152,6 +166,7 @@ func (c *applicationUsecaseImpl) PublishApplicationTemplate(ctx context.Context, func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apisv1.CreateApplicationRequest) (*apisv1.ApplicationBase, error) { application := model.Application{ Name: req.Name, + Alias: req.Alias, Description: req.Description, Namespace: req.Namespace, Icon: req.Icon, @@ -357,6 +372,7 @@ func (c *applicationUsecaseImpl) DetailComponent(ctx context.Context, app *model func (c *applicationUsecaseImpl) converComponentModelToBase(m *model.ApplicationComponent) *apisv1.ComponentBase { return &apisv1.ComponentBase{ Name: m.Name, + Alias: m.Alias, Description: m.Description, Labels: m.Labels, ComponentType: m.Type, @@ -629,6 +645,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo func (c *applicationUsecaseImpl) converAppModelToBase(ctx context.Context, app *model.Application) *apisv1.ApplicationBase { appBeas := &apisv1.ApplicationBase{ Name: app.Name, + Alias: app.Alias, Namespace: app.Namespace, CreateTime: app.CreateTime, UpdateTime: app.UpdateTime, @@ -720,6 +737,7 @@ func (c *applicationUsecaseImpl) AddComponent(ctx context.Context, app *model.Ap Name: com.Name, Type: com.ComponentType, DependsOn: com.DependsOn, + Alias: com.Alias, } properties, err := model.NewJSONStructByString(com.Properties) if err != nil { diff --git a/pkg/apiserver/rest/usecase/application_test.go b/pkg/apiserver/rest/usecase/application_test.go index bd5c81aa3..b1e20e961 100644 --- a/pkg/apiserver/rest/usecase/application_test.go +++ b/pkg/apiserver/rest/usecase/application_test.go @@ -140,7 +140,7 @@ var _ = Describe("Test application usecase function", func() { }) It("Test ListApplications function", func() { - apps, err := appUsecase.ListApplications(context.TODO()) + apps, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{}) Expect(err).Should(BeNil()) Expect(cmp.Diff(len(apps), 3)).Should(BeEmpty()) }) diff --git a/pkg/apiserver/rest/usecase/cluster.go b/pkg/apiserver/rest/usecase/cluster.go index 31f405fa1..29c46988f 100644 --- a/pkg/apiserver/rest/usecase/cluster.go +++ b/pkg/apiserver/rest/usecase/cluster.go @@ -174,6 +174,7 @@ func createClusterModelFromRequest(req apis.CreateClusterRequest, oldCluster *mo newCluster = &model.Cluster{} } newCluster.Name = req.Name + newCluster.Alias = req.Alias newCluster.Description = req.Description newCluster.Icon = req.Icon newCluster.Labels = req.Labels @@ -194,10 +195,11 @@ func (c *clusterUsecaseImpl) createKubeCluster(ctx context.Context, req apis.Cre cluster.SetUpdateTime(t) if providerCluster != nil { cluster.Provider = model.ProviderInfo{ - Name: providerCluster.Name, - ID: providerCluster.ID, - Zone: providerCluster.Zone, - Labels: providerCluster.Labels, + Provider: providerCluster.Provider, + ClusterName: providerCluster.Name, + ID: providerCluster.ID, + Zone: providerCluster.Zone, + Labels: providerCluster.Labels, } cluster.DashboardURL = providerCluster.DashBoardURL } @@ -431,6 +433,7 @@ func (c *clusterUsecaseImpl) ConnectCloudCluster(ctx context.Context, provider s } createReq := apis.CreateClusterRequest{ Name: req.Name, + Alias: req.Alias, Description: req.Description, Icon: req.Icon, Labels: req.Labels, @@ -442,6 +445,7 @@ func (c *clusterUsecaseImpl) ConnectCloudCluster(ctx context.Context, provider s func newClusterBaseFromCluster(cluster *model.Cluster) *apis.ClusterBase { return &apis.ClusterBase{ Name: cluster.Name, + Alias: cluster.Alias, Description: cluster.Description, Icon: cluster.Icon, Labels: cluster.Labels, diff --git a/pkg/apiserver/rest/utils/bcode/addon.go b/pkg/apiserver/rest/utils/bcode/addon.go index 4b1ef71a0..a7ba3cf29 100644 --- a/pkg/apiserver/rest/utils/bcode/addon.go +++ b/pkg/apiserver/rest/utils/bcode/addon.go @@ -19,9 +19,9 @@ package bcode var ( // ErrAddonNotExist addon not exist - ErrAddonNotExist = NewBcode(400, 50001, "addon not exist") + ErrAddonNotExist = NewBcode(404, 50001, "addon not exist") - // ErrAddonRegistryExist application is exist + // ErrAddonRegistryExist addon is exist ErrAddonRegistryExist = NewBcode(400, 50002, "addon name already exists") // ErrAddonRenderFail fail to render addon application diff --git a/pkg/apiserver/rest/utils/uiswagger.go b/pkg/apiserver/rest/utils/uiswagger.go new file mode 100644 index 000000000..fca084eaf --- /dev/null +++ b/pkg/apiserver/rest/utils/uiswagger.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 utils + +// UIParameter Structured import table simple UI model +type UIParameter struct { + Label string `json:"label"` + Description string `json:"description"` + Validate *Validate `json:"validete,omitempty"` + JSONKey string `json:"jsonKey"` + UIType string `json:"uiType"` + // means only can be read. + Disable bool `json:"disable"` + SubParameters []*UIParameter `json:"subParameters,omitempty"` +} + +// Validate parameter validate rule +type Validate struct { + Required bool `json:"required,omitempty"` + Max int `json:"max,omitempty"` + Min int `json:"min,omitempty"` + Regular string `json:"regular,omitempty"` + Options []*Options `json:"options,omitempty"` + DefaultValue interface{} `json:"defaultValue,omitempty"` +} + +// Options select option +type Options struct { + Label string `json:"label"` + Value string `json:"value"` +} + +// ParseUIParameterFromDefinition cue of parameter in Definitions was analyzed to obtain the form description model. +func ParseUIParameterFromDefinition(definition []byte) ([]*UIParameter, error) { + var params []*UIParameter + + return params, nil +} diff --git a/pkg/apiserver/rest/webservice/addon.go b/pkg/apiserver/rest/webservice/addon.go index 52e18f988..57f0469ca 100644 --- a/pkg/apiserver/rest/webservice/addon.go +++ b/pkg/apiserver/rest/webservice/addon.go @@ -19,6 +19,7 @@ package webservice import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/emicklei/go-restful/v3" + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" "github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase" "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode" @@ -48,6 +49,7 @@ func (s *addonWebService) GetWebService() *restful.WebService { ws.Route(ws.GET("/").To(s.listAddons). Doc("list all addons"). Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")). Returns(200, "", apis.ListAddonResponse{}). Returns(400, "", bcode.Bcode{}). Writes(apis.ListAddonResponse{})) @@ -59,41 +61,41 @@ func (s *addonWebService) GetWebService() *restful.WebService { Metadata(restfulspec.KeyOpenAPITags, tags). Returns(200, "", apis.DetailAddonResponse{}). Returns(400, "", bcode.Bcode{}). - Param(ws.QueryParameter("name", "addon name to query detail").DataType("string").Required(true)). + Param(ws.PathParameter("name", "addon name to query detail").DataType("string").Required(true)). Writes(apis.DetailAddonResponse{})) // GET status - ws.Route(ws.GET("/status").To(s.statusAddon). + ws.Route(ws.GET("/{name}/status").To(s.statusAddon). Doc("show status of an addon"). Metadata(restfulspec.KeyOpenAPITags, tags). Returns(200, "", apis.AddonStatusResponse{}). Returns(400, "", bcode.Bcode{}). - Param(ws.QueryParameter("name", "addon name to query status").DataType("string").Required(true)). + Param(ws.PathParameter("name", "addon name to query status").DataType("string").Required(true)). Writes(apis.AddonStatusResponse{})) // enable addon - ws.Route(ws.POST("/enable").To(s.enableAddon). + ws.Route(ws.POST("/{name}/enable").To(s.enableAddon). Doc("enable an addon"). Metadata(restfulspec.KeyOpenAPITags, tags). Returns(200, "", apis.AddonStatusResponse{}). Returns(400, "", bcode.Bcode{}). - Param(ws.QueryParameter("name", "addon name to enable").DataType("string").Required(true)). + Param(ws.PathParameter("name", "addon name to enable").DataType("string").Required(true)). Writes(apis.AddonStatusResponse{})) // disable addon - ws.Route(ws.POST("/disable").To(s.disableAddon). + ws.Route(ws.POST("/{name}/disable").To(s.disableAddon). Doc("disable an addon"). Metadata(restfulspec.KeyOpenAPITags, tags). Returns(200, "", apis.AddonStatusResponse{}). Returns(400, "", bcode.Bcode{}). - Param(ws.QueryParameter("name", "addon name to enable").DataType("string").Required(true)). + Param(ws.PathParameter("name", "addon name to enable").DataType("string").Required(true)). Writes(apis.AddonStatusResponse{})) return ws } func (s *addonWebService) listAddons(req *restful.Request, res *restful.Response) { - detailAddons, err := s.addonUsecase.ListAddons(req.Request.Context(), false) + detailAddons, err := s.addonUsecase.ListAddons(req.Request.Context(), false, req.QueryParameter("query")) if err != nil { bcode.ReturnError(req, res, err) return @@ -113,7 +115,7 @@ func (s *addonWebService) listAddons(req *restful.Request, res *restful.Response } func (s *addonWebService) detailAddon(req *restful.Request, res *restful.Response) { - name := req.QueryParameter("name") + name := req.PathParameter("name") addon, err := s.addonUsecase.GetAddon(req.Request.Context(), name) if err != nil { bcode.ReturnError(req, res, err) @@ -140,7 +142,7 @@ func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Respons return } - name := req.QueryParameter("name") + name := req.PathParameter("name") err = s.addonUsecase.EnableAddon(req.Request.Context(), name, createReq) if err != nil { bcode.ReturnError(req, res, err) @@ -151,7 +153,7 @@ func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Respons } func (s *addonWebService) disableAddon(req *restful.Request, res *restful.Response) { - name := req.QueryParameter("name") + name := req.PathParameter("name") err := s.addonUsecase.DisableAddon(req.Request.Context(), name) if err != nil { bcode.ReturnError(req, res, err) @@ -161,7 +163,7 @@ func (s *addonWebService) disableAddon(req *restful.Request, res *restful.Respon } func (s *addonWebService) statusAddon(req *restful.Request, res *restful.Response) { - name := req.QueryParameter("name") + name := req.PathParameter("name") status, err := s.addonUsecase.StatusAddon(name) if err != nil { bcode.ReturnError(req, res, err) diff --git a/pkg/apiserver/rest/webservice/addon_registry.go b/pkg/apiserver/rest/webservice/addon_registry.go index 8b45fb84f..d6d0eab3b 100644 --- a/pkg/apiserver/rest/webservice/addon_registry.go +++ b/pkg/apiserver/rest/webservice/addon_registry.go @@ -56,6 +56,13 @@ func (s *addonRegistryWebService) GetWebService() *restful.WebService { Returns(400, "", bcode.Bcode{}). Writes(apis.AddonRegistryMeta{})) + ws.Route(ws.GET("/").To(s.listAddonRegistry). + Doc("list all addon registry"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "", apis.ListAddonRegistryResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.ListAddonRegistryResponse{})) + // Delete ws.Route(ws.DELETE("/{name}").To(s.deleteAddonRegistry). Doc("delete an addon registry"). @@ -107,3 +114,15 @@ func (s *addonRegistryWebService) deleteAddonRegistry(req *restful.Request, res return } } + +func (s *addonRegistryWebService) listAddonRegistry(req *restful.Request, res *restful.Response) { + registrys, err := s.addonUsecase.ListAddonRegistries(req.Request.Context()) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := res.WriteEntity(apis.ListAddonRegistryResponse{Registrys: registrys}); err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/rest/webservice/application.go b/pkg/apiserver/rest/webservice/application.go index f848749ea..77d06273b 100644 --- a/pkg/apiserver/rest/webservice/application.go +++ b/pkg/apiserver/rest/webservice/application.go @@ -222,7 +222,11 @@ func (c *applicationWebService) createApplication(req *restful.Request, res *res } func (c *applicationWebService) listApplications(req *restful.Request, res *restful.Response) { - apps, err := c.applicationUsecase.ListApplications(req.Request.Context()) + apps, err := c.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicatioOptions{ + Namespace: req.QueryParameter("namespace"), + Cluster: req.QueryParameter("cluster"), + Query: req.QueryParameter("query"), + }) if err != nil { bcode.ReturnError(req, res, err) return diff --git a/pkg/apiserver/rest/webservice/validate.go b/pkg/apiserver/rest/webservice/validate.go index c0419df89..f5714e80b 100644 --- a/pkg/apiserver/rest/webservice/validate.go +++ b/pkg/apiserver/rest/webservice/validate.go @@ -35,6 +35,9 @@ func init() { if err := validate.RegisterValidation("checkname", ValidateName); err != nil { panic(err) } + if err := validate.RegisterValidation("checkalias", ValidateAlias); err != nil { + panic(err) + } } // ValidateName custom check name field @@ -45,3 +48,12 @@ func ValidateName(fl validator.FieldLevel) bool { } return nameRegexp.MatchString(value) } + +// ValidateAlias custom check alias field +func ValidateAlias(fl validator.FieldLevel) bool { + value := fl.Field().String() + if value != "" && (len(value) > 64 || len(value) < 2) { + return false + } + return true +} diff --git a/pkg/cloudprovider/aliyun.go b/pkg/cloudprovider/aliyun.go index 6ccf9985b..fd7e8cd28 100644 --- a/pkg/cloudprovider/aliyun.go +++ b/pkg/cloudprovider/aliyun.go @@ -116,6 +116,7 @@ func (provider *AliyunCloudProvider) GetClusterInfo(clusterID string) (*CloudClu labels := provider.decodeClusterLabels(cluster.Tags) url := provider.decodeClusterURL(*cluster.MasterUrl) return &CloudCluster{ + Provider: ProviderAliyun, ID: *cluster.ClusterId, Name: *cluster.Name, Type: *cluster.ClusterType, diff --git a/pkg/cloudprovider/types.go b/pkg/cloudprovider/types.go index 319643d06..5898ee433 100644 --- a/pkg/cloudprovider/types.go +++ b/pkg/cloudprovider/types.go @@ -23,6 +23,7 @@ const ( // CloudCluster describes the interface that cloud provider should return type CloudCluster struct { + Provider string `json:"provider"` ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` diff --git a/test/e2e-apiserver-test/addon_test.go b/test/e2e-apiserver-test/addon_test.go index d31549847..3b8e40220 100644 --- a/test/e2e-apiserver-test/addon_test.go +++ b/test/e2e-apiserver-test/addon_test.go @@ -85,7 +85,7 @@ var _ = Describe("Test addon rest api", func() { Args: map[string]string{}, } testAddon := "fluxcd" - res := post("/api/v1/addons/enable?name="+testAddon, req) + res := post("/api/v1/addons/"+testAddon+"/enable", req) Expect(res).ShouldNot(BeNil()) Expect(res.StatusCode).Should(Equal(200)) Expect(res.Body).ShouldNot(BeNil()) @@ -103,7 +103,7 @@ var _ = Describe("Test addon rest api", func() { period := 20 * time.Second timeout := 5 * time.Minute err = wait.PollImmediate(period, timeout, func() (done bool, err error) { - res = get("/api/v1/addons/status?name=" + testAddon) + res = get("/api/v1/addons/" + testAddon + "/status") err = json.NewDecoder(res.Body).Decode(&statusRes) Expect(err).Should(BeNil()) if statusRes.Phase == apis.AddonPhaseEnabled { @@ -113,7 +113,7 @@ var _ = Describe("Test addon rest api", func() { }) Expect(err).Should(BeNil()) - res = post("/api/v1/addons/disable?name="+testAddon, req) + res = post("/api/v1/addons/"+testAddon+"/disable", req) Expect(res).ShouldNot(BeNil()) Expect(res.StatusCode).Should(Equal(200)) Expect(res.Body).ShouldNot(BeNil())