feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
84
pkg/kapis/application/v2/attachments.go
Normal file
84
pkg/kapis/application/v2/attachments.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
AttachmentContent map[string]strfmt.Base64 `json:"attachment_content,omitempty"`
|
||||
AttachmentID string `json:"attachment_id,omitempty"`
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeAttachment(req *restful.Request, resp *restful.Response) {
|
||||
attachmentId := req.PathParameter("attachment")
|
||||
data, err := application.FailOverGet(h.cmStore, h.ossStore, attachmentId, h.client, false)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
result := &Attachment{AttachmentID: attachmentId,
|
||||
AttachmentContent: map[string]strfmt.Base64{
|
||||
"raw": data,
|
||||
},
|
||||
}
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *appHandler) CreateAttachment(req *restful.Request, resp *restful.Response) {
|
||||
err := req.Request.ParseMultipartForm(10 << 20)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
var att *Attachment
|
||||
// just save one attachment
|
||||
for fName := range req.Request.MultipartForm.File {
|
||||
f, _, err := req.Request.FormFile(fName)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
data, _ := io.ReadAll(f)
|
||||
f.Close()
|
||||
|
||||
id := application.GetUuid36(fmt.Sprintf("%s-att-", fName))
|
||||
|
||||
err = application.FailOverUpload(h.cmStore, h.ossStore, id, bytes.NewBuffer(data), len(data))
|
||||
if err != nil {
|
||||
klog.Errorf("upload attachment failed, err: %s", err)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
klog.V(4).Infof("upload attachment success")
|
||||
att = &Attachment{AttachmentID: id}
|
||||
break
|
||||
}
|
||||
|
||||
resp.WriteEntity(att)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteAttachments(req *restful.Request, resp *restful.Response) {
|
||||
attachmentId := req.PathParameter("attachment")
|
||||
ids := strings.Split(attachmentId, ",")
|
||||
err := application.FailOverDelete(h.cmStore, h.ossStore, ids)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
105
pkg/kapis/application/v2/crd2cr.go
Normal file
105
pkg/kapis/application/v2/crd2cr.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
)
|
||||
|
||||
func (h *appHandler) exampleCr(req *restful.Request, resp *restful.Response) {
|
||||
name := req.PathParameter("name")
|
||||
crd := v1.CustomResourceDefinition{}
|
||||
err := h.client.Get(req.Request.Context(), client.ObjectKey{Name: name}, &crd)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
cr, err := convertCRDToCR(crd)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
resp.WriteEntity(cr)
|
||||
}
|
||||
|
||||
func convertCRDToCR(crd v1.CustomResourceDefinition) (dstCr unstructured.Unstructured, err error) {
|
||||
|
||||
cr := unstructured.Unstructured{}
|
||||
cr.SetName(fmt.Sprintf("%s-Instance", crd.Spec.Names.Singular))
|
||||
cr.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: crd.Spec.Group,
|
||||
Kind: crd.Spec.Names.Kind,
|
||||
})
|
||||
|
||||
var selectedVersion *v1.CustomResourceDefinitionVersion
|
||||
for _, version := range crd.Spec.Versions {
|
||||
if version.Served && version.Storage {
|
||||
selectedVersion = &version
|
||||
break
|
||||
}
|
||||
}
|
||||
if selectedVersion == nil {
|
||||
return dstCr, errors.New("no served and storage version found in CRD")
|
||||
}
|
||||
cr.SetAPIVersion(selectedVersion.Name)
|
||||
|
||||
generateProps(selectedVersion, cr, "spec")
|
||||
generateProps(selectedVersion, cr, "status")
|
||||
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
func generateProps(selectedVersion *v1.CustomResourceDefinitionVersion, cr unstructured.Unstructured, name string) {
|
||||
data := make(map[string]any)
|
||||
specProps := selectedVersion.Schema.OpenAPIV3Schema.Properties[name].Properties
|
||||
for key, value := range specProps {
|
||||
data[key] = getDefaultValue(value)
|
||||
}
|
||||
cr.Object[name] = data
|
||||
}
|
||||
|
||||
func getDefaultValue(value v1.JSONSchemaProps) any {
|
||||
switch value.Type {
|
||||
case "object":
|
||||
return parseObject(value.Properties)
|
||||
case "integer":
|
||||
if value.Minimum != nil {
|
||||
return *value.Minimum
|
||||
}
|
||||
return 0
|
||||
case "boolean":
|
||||
return false
|
||||
case "array":
|
||||
if value.Items.Schema != nil {
|
||||
return []any{getDefaultValue(*value.Items.Schema)}
|
||||
}
|
||||
return []any{}
|
||||
case "string":
|
||||
if len(value.Enum) > 0 {
|
||||
return string(value.Enum[0].Raw)
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func parseObject(obj map[string]v1.JSONSchemaProps) map[string]any {
|
||||
res := make(map[string]any)
|
||||
for key, value := range obj {
|
||||
res[key] = getDefaultValue(value)
|
||||
}
|
||||
return res
|
||||
}
|
||||
105
pkg/kapis/application/v2/errors.go
Normal file
105
pkg/kapis/application/v2/errors.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
goruntime "runtime"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
resv1beta1 "kubesphere.io/kubesphere/pkg/models/resources/v1beta1"
|
||||
)
|
||||
|
||||
func (h *appHandler) conflictedDone(req *restful.Request, resp *restful.Response, pathParam string, obj client.Object) bool {
|
||||
existed, err := h.checkConflicted(req, pathParam, obj)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return true
|
||||
}
|
||||
if existed {
|
||||
kind := obj.GetObjectKind().GroupVersionKind().Kind
|
||||
api.HandleConflict(resp, req, fmt.Errorf("%s %s already exists", kind, obj.GetName()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *appHandler) checkConflicted(req *restful.Request, pathParam string, obj client.Object) (bool, error) {
|
||||
|
||||
key := runtimeclient.ObjectKey{Name: obj.GetName(), Namespace: obj.GetNamespace()}
|
||||
if req.PathParameter(pathParam) != "" {
|
||||
//if route like /repos/{repo} and request path has repo, then it's update, filling obj
|
||||
err := h.client.Get(req.Request.Context(), key, obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
//if route like /repos, then it's create
|
||||
err := h.client.Get(req.Request.Context(), key, obj)
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func requestDone(err error, resp *restful.Response) bool {
|
||||
|
||||
_, file, line, _ := goruntime.Caller(1)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
api.HandleNotFound(resp, nil, err)
|
||||
return true
|
||||
}
|
||||
klog.Errorf("%s:%d request done with error: %v", file, line, err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func removeQueryArg(req *restful.Request, args ...string) {
|
||||
//The default filter is a whitelist, so delete some of our custom logical parameters
|
||||
for _, i := range args {
|
||||
q := req.Request.URL.Query()
|
||||
q.Del(i)
|
||||
req.Request.URL.RawQuery = q.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
func convertToListResult(obj runtime.Object, req *restful.Request) (listResult api.ListResult) {
|
||||
removeQueryArg(req, params.ConditionsParam, "global", "create")
|
||||
_ = meta.EachListItem(obj, omitManagedFields)
|
||||
queryParams := query.ParseQueryParameter(req)
|
||||
list, _ := meta.ExtractList(obj)
|
||||
items, _, totalCount := resv1beta1.DefaultList(list, queryParams, resv1beta1.DefaultCompare, resv1beta1.DefaultFilter)
|
||||
|
||||
listResult.Items = items
|
||||
listResult.TotalItems = totalCount
|
||||
|
||||
return listResult
|
||||
}
|
||||
func omitManagedFields(o runtime.Object) error {
|
||||
a, err := meta.Accessor(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.SetManagedFields(nil)
|
||||
return nil
|
||||
}
|
||||
342
pkg/kapis/application/v2/handler_app.go
Normal file
342
pkg/kapis/application/v2/handler_app.go
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"mime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog/v2"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
)
|
||||
|
||||
const maxFileSize = 1 * 1024 * 1024 // 1 MB in bytes
|
||||
|
||||
func (h *appHandler) CreateOrUpdateApp(req *restful.Request, resp *restful.Response) {
|
||||
createAppRequest, err := parseUpload(req)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if h.ossStore == nil && len(createAppRequest.Package) > maxFileSize {
|
||||
api.HandleBadRequest(resp, nil, fmt.Errorf("System has no OSS store, the maximum file size is %d", maxFileSize))
|
||||
return
|
||||
}
|
||||
|
||||
newReq, err := parseRequest(createAppRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
data := map[string]any{
|
||||
"icon": newReq.Icon,
|
||||
"appName": newReq.AppName,
|
||||
"versionName": newReq.VersionName,
|
||||
"appHome": newReq.AppHome,
|
||||
"description": newReq.Description,
|
||||
"aliasName": newReq.AliasName,
|
||||
"resources": newReq.Resources,
|
||||
}
|
||||
|
||||
validate, _ := strconv.ParseBool(req.QueryParameter("validate"))
|
||||
if validate {
|
||||
resp.WriteAsJson(data)
|
||||
return
|
||||
}
|
||||
|
||||
app := &appv2.Application{}
|
||||
app.Name = newReq.AppName
|
||||
if h.conflictedDone(req, resp, "app", app) {
|
||||
return
|
||||
}
|
||||
|
||||
newReq.FromRepo = false
|
||||
vRequests := []application.AppRequest{newReq}
|
||||
err = application.CreateOrUpdateApp(h.client, vRequests, h.cmStore, h.ossStore)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(data)
|
||||
}
|
||||
|
||||
func parseUpload(req *restful.Request) (createAppRequest application.AppRequest, err error) {
|
||||
contentType := req.Request.Header.Get("Content-Type")
|
||||
mediaType, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
klog.Errorf("parse media type failed, err: %s", err)
|
||||
return createAppRequest, err
|
||||
}
|
||||
if mediaType == "multipart/form-data" {
|
||||
file, header, err := req.Request.FormFile("file")
|
||||
if err != nil {
|
||||
klog.Errorf("parse form file failed, err: %s", err)
|
||||
return createAppRequest, err
|
||||
}
|
||||
klog.Info("upload file:", header.Filename)
|
||||
defer file.Close()
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
klog.Errorf("read file failed, err: %s", err)
|
||||
return createAppRequest, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(req.Request.FormValue("jsonData")), &createAppRequest)
|
||||
if err != nil {
|
||||
klog.Errorf("parse json data failed, err: %s", err)
|
||||
return createAppRequest, err
|
||||
}
|
||||
if createAppRequest.Package != nil {
|
||||
return createAppRequest, errors.New("When using multipart/form-data to upload files, the package field does not need to be included in the json data.")
|
||||
}
|
||||
createAppRequest.Package = data
|
||||
return createAppRequest, nil
|
||||
}
|
||||
if mediaType == "application/json" {
|
||||
err = req.ReadEntity(&createAppRequest)
|
||||
if err != nil {
|
||||
klog.Errorf("parse json data failed, err: %s", err)
|
||||
return createAppRequest, err
|
||||
}
|
||||
return createAppRequest, nil
|
||||
}
|
||||
return createAppRequest, errors.New("unsupported media type")
|
||||
}
|
||||
|
||||
func (h *appHandler) ListApps(req *restful.Request, resp *restful.Response) {
|
||||
workspace := req.PathParameter("workspace")
|
||||
conditions, err := params.ParseConditions(req)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
opt := runtimeclient.ListOptions{}
|
||||
labelSelectorStr := req.QueryParameter("labelSelector")
|
||||
labelSelector, err := labels.Parse(labelSelectorStr)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
opt.LabelSelector = labelSelector
|
||||
|
||||
result := appv2.ApplicationList{}
|
||||
err = h.client.List(req.Request.Context(), &result, &opt)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
filtered := appv2.ApplicationList{}
|
||||
for _, app := range result.Items {
|
||||
curApp := app
|
||||
states := strings.Split(conditions.Match[Status], "|")
|
||||
if conditions.Match[Status] != "" && !sliceutil.HasString(states, curApp.Status.State) {
|
||||
continue
|
||||
}
|
||||
allowList := []string{appv2.SystemWorkspace, workspace}
|
||||
if workspace != "" && !stringutils.StringIn(app.Labels[constants.WorkspaceLabelKey], allowList) {
|
||||
continue
|
||||
}
|
||||
filtered.Items = append(filtered.Items, curApp)
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(&filtered, req))
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeApp(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
key := runtimeclient.ObjectKey{Name: req.PathParameter("app")}
|
||||
app := &appv2.Application{}
|
||||
err := h.client.Get(req.Request.Context(), key, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
app.SetManagedFields(nil)
|
||||
|
||||
resp.WriteEntity(app)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteApp(req *restful.Request, resp *restful.Response) {
|
||||
appId := req.PathParameter("app")
|
||||
app := &appv2.Application{}
|
||||
|
||||
err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: appId}, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
err = application.FailOverDelete(h.cmStore, h.ossStore, app.Spec.Attachments)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.client.Delete(req.Request.Context(), &appv2.Application{ObjectMeta: metav1.ObjectMeta{Name: appId}})
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) DoAppAction(req *restful.Request, resp *restful.Response) {
|
||||
var doActionRequest appv2.ApplicationVersionStatus
|
||||
err := req.ReadEntity(&doActionRequest)
|
||||
if err != nil {
|
||||
klog.V(4).Infoln(err)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
ctx := req.Request.Context()
|
||||
|
||||
user, _ := request.UserFrom(req.Request.Context())
|
||||
if user != nil {
|
||||
doActionRequest.UserName = user.GetName()
|
||||
}
|
||||
app := &appv2.Application{}
|
||||
app.Name = req.PathParameter("app")
|
||||
|
||||
err = h.client.Get(ctx, runtimeclient.ObjectKey{Name: app.Name}, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
// app state check, draft -> active -> suspended -> active
|
||||
switch doActionRequest.State {
|
||||
case appv2.ReviewStatusActive:
|
||||
if app.Status.State != appv2.ReviewStatusDraft &&
|
||||
app.Status.State != appv2.ReviewStatusSuspended {
|
||||
err = fmt.Errorf("app %s is not in draft or suspended status", app.Name)
|
||||
break
|
||||
}
|
||||
// active state is only allowed if at least one app version is in active or passed state
|
||||
appVersionList := &appv2.ApplicationVersionList{}
|
||||
opt := &runtimeclient.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{appv2.AppIDLabelKey: app.Name})}
|
||||
h.client.List(ctx, appVersionList, opt)
|
||||
okActive := false
|
||||
if len(appVersionList.Items) > 0 {
|
||||
for _, v := range appVersionList.Items {
|
||||
if v.Status.State == appv2.ReviewStatusActive ||
|
||||
v.Status.State == appv2.ReviewStatusPassed {
|
||||
okActive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !okActive {
|
||||
err = fmt.Errorf("app %s has no active or passed appversion", app.Name)
|
||||
}
|
||||
case appv2.ReviewStatusSuspended:
|
||||
if app.Status.State != appv2.ReviewStatusActive {
|
||||
err = fmt.Errorf("app %s is not in active status", app.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
if doActionRequest.State == appv2.ReviewStatusActive {
|
||||
if app.Labels == nil {
|
||||
app.Labels = map[string]string{}
|
||||
}
|
||||
app.Labels[appv2.AppStoreLabelKey] = "true"
|
||||
h.client.Update(ctx, app)
|
||||
}
|
||||
|
||||
app.Status.State = doActionRequest.State
|
||||
app.Status.UpdateTime = &metav1.Time{Time: time.Now()}
|
||||
err = h.client.Status().Update(ctx, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
//update appversion status
|
||||
versions := &appv2.ApplicationVersionList{}
|
||||
|
||||
opt := &runtimeclient.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labels.Set{appv2.AppIDLabelKey: app.Name}),
|
||||
}
|
||||
|
||||
err = h.client.List(ctx, versions, opt)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
for _, version := range versions.Items {
|
||||
if version.Status.State == appv2.StatusActive || version.Status.State == appv2.ReviewStatusSuspended {
|
||||
err = DoAppVersionAction(ctx, version.Name, doActionRequest, h.client)
|
||||
if err != nil {
|
||||
klog.V(4).Infoln(err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) PatchApp(req *restful.Request, resp *restful.Response) {
|
||||
var err error
|
||||
appId := req.PathParameter("app")
|
||||
appBody := &application.AppRequest{}
|
||||
err = req.ReadEntity(appBody)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
ctx := req.Request.Context()
|
||||
|
||||
app := &appv2.Application{}
|
||||
app.Name = appId
|
||||
err = h.client.Get(ctx, runtimeclient.ObjectKey{Name: app.Name}, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
if app.GetLabels() == nil {
|
||||
app.SetLabels(map[string]string{})
|
||||
}
|
||||
app.Labels[appv2.AppCategoryNameKey] = appBody.CategoryName
|
||||
app.Spec.Icon = appBody.Icon
|
||||
ant := app.GetAnnotations()
|
||||
if ant == nil {
|
||||
ant = make(map[string]string)
|
||||
}
|
||||
|
||||
ant[constants.DescriptionAnnotationKey] = appBody.Description
|
||||
ant[constants.DisplayNameAnnotationKey] = appBody.AliasName
|
||||
app.SetAnnotations(ant)
|
||||
|
||||
app.Spec.Attachments = appBody.Attachments
|
||||
app.Spec.Abstraction = appBody.Abstraction
|
||||
app.Spec.AppHome = appBody.AppHome
|
||||
|
||||
err = h.client.Update(ctx, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
klog.V(4).Infof("update app %s successfully", app.Name)
|
||||
|
||||
resp.WriteAsJson(app)
|
||||
}
|
||||
296
pkg/kapis/application/v2/handler_apprls.go
Normal file
296
pkg/kapis/application/v2/handler_apprls.go
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"golang.org/x/net/context"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
"kubesphere.io/api/constants"
|
||||
"kubesphere.io/utils/helm"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
)
|
||||
|
||||
func (h *appHandler) CreateOrUpdateAppRls(req *restful.Request, resp *restful.Response) {
|
||||
var createRlsRequest appv2.ApplicationRelease
|
||||
err := req.ReadEntity(&createRlsRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
list := []string{appv2.AppIDLabelKey, constants.ClusterNameLabelKey, constants.WorkspaceLabelKey, constants.NamespaceLabelKey}
|
||||
for _, i := range list {
|
||||
value, ok := createRlsRequest.GetLabels()[i]
|
||||
if !ok || value == "" {
|
||||
err = errors.New("must set %s", i)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
apprls := appv2.ApplicationRelease{}
|
||||
apprls.Name = createRlsRequest.Name
|
||||
|
||||
if h.conflictedDone(req, resp, "application", &apprls) {
|
||||
return
|
||||
}
|
||||
|
||||
if createRlsRequest.Spec.AppType != appv2.AppTypeHelm {
|
||||
runtimeClient, _, _, err := h.getCluster(createRlsRequest.GetRlsCluster())
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
template, err := application.FailOverGet(h.cmStore, h.ossStore, createRlsRequest.Spec.AppVersionID, h.client, true)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
_, err = application.ComplianceCheck(createRlsRequest.Spec.Values, template,
|
||||
runtimeClient.RESTMapper(), createRlsRequest.GetRlsNamespace())
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user, _ := request.UserFrom(req.Request.Context())
|
||||
creator := ""
|
||||
if user != nil {
|
||||
creator = user.GetName()
|
||||
}
|
||||
|
||||
copyRls := apprls.DeepCopy()
|
||||
mutateFn := func() error {
|
||||
createRlsRequest.DeepCopyInto(&apprls)
|
||||
apprls.ResourceVersion = copyRls.ResourceVersion
|
||||
if apprls.Labels == nil {
|
||||
apprls.Labels = map[string]string{}
|
||||
}
|
||||
apprls.Labels[appv2.AppVersionIDLabelKey] = createRlsRequest.Spec.AppVersionID
|
||||
if apprls.Annotations == nil {
|
||||
apprls.Annotations = map[string]string{}
|
||||
}
|
||||
apprls.Annotations[constants.CreatorAnnotationKey] = creator
|
||||
return nil
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(req.Request.Context(), h.client, &apprls, mutateFn)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeAppRls(req *restful.Request, resp *restful.Response) {
|
||||
applicationId := req.PathParameter("application")
|
||||
ctx := req.Request.Context()
|
||||
|
||||
key := runtimeclient.ObjectKey{Name: applicationId}
|
||||
app := &appv2.ApplicationRelease{}
|
||||
err := h.client.Get(ctx, key, app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
app.SetManagedFields(nil)
|
||||
if app.Spec.AppType == appv2.AppTypeYaml || app.Spec.AppType == appv2.AppTypeEdge {
|
||||
data, err := h.getRealTimeYaml(ctx, app)
|
||||
if err != nil {
|
||||
klog.Errorf("getRealTimeYaml: %s", err.Error())
|
||||
app.Status.RealTimeResources = nil
|
||||
resp.WriteEntity(app)
|
||||
return
|
||||
}
|
||||
app.Status.RealTimeResources = data
|
||||
resp.WriteEntity(app)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.getRealTimeHelm(ctx, app)
|
||||
if err != nil {
|
||||
klog.Errorf("getRealTimeHelm: %s", err.Error())
|
||||
app.Status.RealTimeResources = nil
|
||||
resp.WriteEntity(app)
|
||||
return
|
||||
}
|
||||
|
||||
app.Status.RealTimeResources = data
|
||||
resp.WriteEntity(app)
|
||||
}
|
||||
|
||||
func (h *appHandler) getRealTimeYaml(ctx context.Context, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) {
|
||||
runtimeClient, dynamicClient, cluster, err := h.getCluster(app.GetRlsCluster())
|
||||
if err != nil {
|
||||
klog.Errorf("cluster: %s url: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsonList, err := application.ReadYaml(app.Spec.Values)
|
||||
if err != nil {
|
||||
klog.Errorf("ReadYaml: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
for _, i := range jsonList {
|
||||
gvr, utd, err := application.GetInfoFromBytes(i, runtimeClient.RESTMapper())
|
||||
if err != nil {
|
||||
klog.Errorf("GetInfoFromBytes: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var utdRealTime *unstructured.Unstructured
|
||||
utdRealTime, err = dynamicClient.Resource(gvr).Namespace(utd.GetNamespace()).
|
||||
Get(ctx, utd.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("cluster: %s url: %s resource: %s/%s/%s: %s",
|
||||
cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, utd.GetNamespace(), gvr.Resource, utd.GetName(), err)
|
||||
realTimeJson := errorRealTime(utd, err.Error())
|
||||
data = append(data, realTimeJson)
|
||||
continue
|
||||
}
|
||||
utdRealTime.SetManagedFields(nil)
|
||||
realTimeJson, err := utdRealTime.MarshalJSON()
|
||||
if err != nil {
|
||||
klog.Errorf("MarshalJSON: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, realTimeJson)
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (h *appHandler) getRealTimeHelm(ctx context.Context, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) {
|
||||
runtimeClient, dynamicClient, cluster, err := h.getCluster(app.GetRlsCluster())
|
||||
if err != nil {
|
||||
klog.Errorf("cluster: %s url: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, err)
|
||||
return nil, err
|
||||
}
|
||||
helmConf, err := helm.InitHelmConf(cluster.Spec.Connection.KubeConfig, app.GetRlsNamespace())
|
||||
if err != nil {
|
||||
klog.Errorf("InitHelmConf cluster: %s url: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, err)
|
||||
return nil, err
|
||||
}
|
||||
rel, err := action.NewGet(helmConf).Run(app.Name)
|
||||
if err != nil {
|
||||
klog.Errorf("cluster: %s url: %s release: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, app.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
resources, _ := helmConf.KubeClient.Build(bytes.NewBufferString(rel.Manifest), true)
|
||||
|
||||
for _, i := range resources {
|
||||
utd, err := application.ConvertToUnstructured(i.Object)
|
||||
if err != nil {
|
||||
klog.Errorf("ConvertToUnstructured: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
marshalJSON, err := utd.MarshalJSON()
|
||||
if err != nil {
|
||||
klog.Errorf("MarshalJSON: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
gvr, utd, err := application.GetInfoFromBytes(marshalJSON, runtimeClient.RESTMapper())
|
||||
if err != nil {
|
||||
klog.Errorf("GetInfoFromBytes: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
utdRealTime, err := dynamicClient.Resource(gvr).Namespace(utd.GetNamespace()).
|
||||
Get(ctx, utd.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("cluster: %s url: %s resource: %s/%s/%s: %s",
|
||||
cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, utd.GetNamespace(), gvr.Resource, utd.GetName(), err)
|
||||
realTimeJson := errorRealTime(utd, err.Error())
|
||||
data = append(data, realTimeJson)
|
||||
continue
|
||||
}
|
||||
utdRealTime.SetManagedFields(nil)
|
||||
realTimeJson, err := utdRealTime.MarshalJSON()
|
||||
if err != nil {
|
||||
klog.Errorf("MarshalJSON: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, realTimeJson)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func errorRealTime(utd *unstructured.Unstructured, msg string) json.RawMessage {
|
||||
fake := &unstructured.Unstructured{}
|
||||
fake.SetKind(utd.GetKind())
|
||||
fake.SetAPIVersion(utd.GetAPIVersion())
|
||||
fake.SetName(utd.GetName())
|
||||
fake.SetNamespace(utd.GetNamespace())
|
||||
fake.SetLabels(utd.GetLabels())
|
||||
fake.SetAnnotations(utd.GetAnnotations())
|
||||
unstructured.SetNestedField(fake.Object, msg, "status", "state")
|
||||
marshalJSON, _ := fake.MarshalJSON()
|
||||
|
||||
return marshalJSON
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteAppRls(req *restful.Request, resp *restful.Response) {
|
||||
applicationId := req.PathParameter("application")
|
||||
|
||||
app := &appv2.ApplicationRelease{}
|
||||
app.Name = applicationId
|
||||
|
||||
err := h.client.Delete(req.Request.Context(), app)
|
||||
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) ListAppRls(req *restful.Request, resp *restful.Response) {
|
||||
labelValues := map[string]string{
|
||||
appv2.AppIDLabelKey: req.QueryParameter("appID"),
|
||||
constants.NamespaceLabelKey: req.PathParameter("namespace"),
|
||||
constants.WorkspaceLabelKey: req.PathParameter("workspace"),
|
||||
constants.ClusterNameLabelKey: req.PathParameter("cluster"),
|
||||
}
|
||||
labelSet := map[string]string{}
|
||||
for key, value := range labelValues {
|
||||
if value != "" {
|
||||
labelSet[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
opt := runtimeclient.ListOptions{}
|
||||
labelSelectorStr := req.QueryParameter("labelSelector")
|
||||
labelSelector, err := labels.Parse(labelSelectorStr)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
for k, v := range labelSet {
|
||||
routeLabel, _ := labels.NewRequirement(k, selection.Equals, []string{v})
|
||||
labelSelector = labelSelector.Add(*routeLabel)
|
||||
}
|
||||
|
||||
opt.LabelSelector = labelSelector
|
||||
|
||||
appList := appv2.ApplicationReleaseList{}
|
||||
err = h.client.List(req.Request.Context(), &appList, &opt)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(&appList, req))
|
||||
}
|
||||
339
pkg/kapis/application/v2/handler_appversion.go
Normal file
339
pkg/kapis/application/v2/handler_appversion.go
Normal file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"golang.org/x/net/context"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/klog/v2"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
)
|
||||
|
||||
func (h *appHandler) CreateOrUpdateAppVersion(req *restful.Request, resp *restful.Response) {
|
||||
createAppVersionRequest, err := parseUpload(req)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
if h.ossStore == nil && len(createAppVersionRequest.Package) > maxFileSize {
|
||||
api.HandleBadRequest(resp, nil, fmt.Errorf("System has no OSS store, the maximum file size is %d", maxFileSize))
|
||||
return
|
||||
}
|
||||
|
||||
createAppVersionRequest.AppName = req.PathParameter("app")
|
||||
|
||||
vRequest, err := parseRequest(createAppVersionRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
data := map[string]any{
|
||||
"icon": vRequest.Icon,
|
||||
"appName": vRequest.AppName,
|
||||
"versionName": vRequest.VersionName,
|
||||
"appHome": vRequest.AppHome,
|
||||
"description": vRequest.Description,
|
||||
"aliasName": vRequest.AliasName,
|
||||
}
|
||||
|
||||
validate, _ := strconv.ParseBool(req.QueryParameter("validate"))
|
||||
if validate {
|
||||
resp.WriteAsJson(data)
|
||||
return
|
||||
}
|
||||
appVersion := &appv2.ApplicationVersion{}
|
||||
vRequest.VersionName = application.FormatVersion(vRequest.VersionName)
|
||||
appVersion.Name = fmt.Sprintf("%s-%s", createAppVersionRequest.AppName, vRequest.VersionName)
|
||||
if h.conflictedDone(req, resp, "version", appVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
app := appv2.Application{}
|
||||
err = h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: createAppVersionRequest.AppName}, &app)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
err = application.CreateOrUpdateAppVersion(req.Request.Context(), h.client, app, vRequest, h.cmStore, h.ossStore)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
err = application.UpdateLatestAppVersion(req.Request.Context(), h.client, app)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to update latest app version, err:%v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(data)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteAppVersion(req *restful.Request, resp *restful.Response) {
|
||||
versionId := req.PathParameter("version")
|
||||
err := h.client.Delete(req.Request.Context(), &appv2.ApplicationVersion{ObjectMeta: metav1.ObjectMeta{Name: versionId}})
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeAppVersion(req *restful.Request, resp *restful.Response) {
|
||||
versionId := req.PathParameter("version")
|
||||
result := &appv2.ApplicationVersion{}
|
||||
err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: versionId}, result)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
result.SetManagedFields(nil)
|
||||
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *appHandler) ListAppVersions(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
var err error
|
||||
workspace := req.PathParameter("workspace")
|
||||
opt := runtimeclient.ListOptions{}
|
||||
labelSelectorStr := req.QueryParameter("labelSelector")
|
||||
labelSelector, err := labels.Parse(labelSelectorStr)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
vals := []string{req.PathParameter("app")}
|
||||
appRequirement, err := labels.NewRequirement(appv2.AppIDLabelKey, selection.Equals, vals)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
labelSelector = labelSelector.Add(*appRequirement)
|
||||
opt.LabelSelector = labelSelector
|
||||
|
||||
result := appv2.ApplicationVersionList{}
|
||||
err = h.client.List(req.Request.Context(), &result, &opt)
|
||||
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
conditions, err := params.ParseConditions(req)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
filtered := appv2.ApplicationVersionList{}
|
||||
for _, appv := range result.Items {
|
||||
states := strings.Split(conditions.Match[Status], "|")
|
||||
if conditions.Match[Status] != "" && !sliceutil.HasString(states, appv.Status.State) {
|
||||
continue
|
||||
}
|
||||
allowList := []string{appv2.SystemWorkspace, workspace}
|
||||
if workspace != "" && !stringutils.StringIn(appv.Labels[constants.WorkspaceLabelKey], allowList) {
|
||||
continue
|
||||
}
|
||||
|
||||
filtered.Items = append(filtered.Items, appv)
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(&filtered, req))
|
||||
}
|
||||
|
||||
func (h *appHandler) GetAppVersionPackage(req *restful.Request, resp *restful.Response) {
|
||||
versionId := req.PathParameter("version")
|
||||
app := req.PathParameter("app")
|
||||
|
||||
data, err := application.FailOverGet(h.cmStore, h.ossStore, versionId, h.client, true)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version %s failed, error: %s", versionId, err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]any{
|
||||
"versionID": versionId,
|
||||
"package": data,
|
||||
"appID": app,
|
||||
}
|
||||
|
||||
resp.WriteAsJson(result)
|
||||
}
|
||||
|
||||
func (h *appHandler) GetAppVersionFiles(req *restful.Request, resp *restful.Response) {
|
||||
versionId := req.PathParameter("version")
|
||||
|
||||
data, err := application.FailOverGet(h.cmStore, h.ossStore, versionId, h.client, true)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version %s failed, error: %s", versionId, err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
var result = make(map[string][]byte)
|
||||
|
||||
chartData, err := loader.LoadArchive(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
result["all.yaml"] = data
|
||||
resp.WriteAsJson(result)
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range chartData.Raw {
|
||||
result[f.Name] = f.Data
|
||||
}
|
||||
|
||||
resp.WriteAsJson(result)
|
||||
}
|
||||
|
||||
func (h *appHandler) AppVersionAction(req *restful.Request, resp *restful.Response) {
|
||||
versionID := req.PathParameter("version")
|
||||
var doActionRequest appv2.ApplicationVersionStatus
|
||||
err := req.ReadEntity(&doActionRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
ctx := req.Request.Context()
|
||||
|
||||
appVersion := &appv2.ApplicationVersion{}
|
||||
err = h.client.Get(ctx, runtimeclient.ObjectKey{Name: versionID}, appVersion)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if doActionRequest.Message == "" {
|
||||
doActionRequest.Message = appVersion.Status.Message
|
||||
}
|
||||
|
||||
// app version check state draft -> submitted -> (rejected -> submitted ) -> passed-> active -> (suspended -> active), draft -> submitted -> active
|
||||
|
||||
err = DoAppVersionAction(ctx, versionID, doActionRequest, h.client)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func DoAppVersionAction(ctx context.Context, versionId string, actionReq appv2.ApplicationVersionStatus, client runtimeclient.Client) error {
|
||||
|
||||
key := runtimeclient.ObjectKey{Name: versionId}
|
||||
version := &appv2.ApplicationVersion{}
|
||||
err := client.Get(ctx, key, version)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version %s failed, error: %s", versionId, err)
|
||||
return err
|
||||
}
|
||||
version.Status.State = actionReq.State
|
||||
if actionReq.Message != "" {
|
||||
version.Status.Message = actionReq.Message
|
||||
}
|
||||
|
||||
if actionReq.UserName != "" {
|
||||
version.Status.UserName = actionReq.UserName
|
||||
}
|
||||
version.Status.Updated = &metav1.Time{Time: metav1.Now().Time}
|
||||
err = client.Status().Update(ctx, version)
|
||||
if err != nil {
|
||||
klog.Errorf("update app version %s failed, error: %s", versionId, err)
|
||||
}
|
||||
|
||||
appID := version.Labels[appv2.AppIDLabelKey]
|
||||
err = checkAppStatus(ctx, appID, actionReq.State, client)
|
||||
if err != nil {
|
||||
klog.Errorf("check app failed, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func checkAppStatus(ctx context.Context, appID, action string, client runtimeclient.Client) error {
|
||||
//If all appVersions are Suspended, then the app's status is not active
|
||||
if action != appv2.ReviewStatusSuspended {
|
||||
return nil
|
||||
}
|
||||
|
||||
allVersion := appv2.ApplicationVersionList{}
|
||||
opt := runtimeclient.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labels.Set{appv2.AppIDLabelKey: appID}),
|
||||
}
|
||||
err := client.List(ctx, &allVersion, &opt)
|
||||
if err != nil {
|
||||
klog.Errorf("list app versions failed, error: %s", err)
|
||||
return err
|
||||
}
|
||||
active := false
|
||||
for _, v := range allVersion.Items {
|
||||
if v.Status.State == appv2.ReviewStatusActive {
|
||||
active = true
|
||||
break
|
||||
}
|
||||
}
|
||||
app := appv2.Application{}
|
||||
err = client.Get(ctx, runtimeclient.ObjectKey{Name: appID}, &app)
|
||||
if err != nil {
|
||||
klog.Errorf("get app %s failed, error: %s", appID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !active && app.Status.State == appv2.ReviewStatusActive {
|
||||
app.Status.State = appv2.ReviewStatusDraft
|
||||
err = client.Status().Update(ctx, &app)
|
||||
if err != nil {
|
||||
klog.Errorf("update app %s failed, error: %s", appID, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *appHandler) ListReviews(req *restful.Request, resp *restful.Response) {
|
||||
conditions, err := params.ParseConditions(req)
|
||||
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
appVersions := appv2.ApplicationVersionList{}
|
||||
opts := runtimeclient.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labels.Set{appv2.RepoIDLabelKey: appv2.UploadRepoKey}),
|
||||
}
|
||||
err = h.client.List(req.Request.Context(), &appVersions, &opts)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if conditions == nil || len(conditions.Match) == 0 {
|
||||
resp.WriteEntity(convertToListResult(&appVersions, req))
|
||||
return
|
||||
}
|
||||
|
||||
filteredAppVersions := appv2.ApplicationVersionList{}
|
||||
states := strings.Split(conditions.Match[Status], "|")
|
||||
for _, version := range appVersions.Items {
|
||||
if conditions.Match[Status] != "" && !sliceutil.HasString(states, version.Status.State) {
|
||||
continue
|
||||
}
|
||||
filteredAppVersions.Items = append(filteredAppVersions.Items, version)
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(&filteredAppVersions, req))
|
||||
}
|
||||
103
pkg/kapis/application/v2/handler_category.go
Normal file
103
pkg/kapis/application/v2/handler_category.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
)
|
||||
|
||||
func (h *appHandler) CreateOrUpdateCategory(req *restful.Request, resp *restful.Response) {
|
||||
createCategoryRequest := &appv2.Category{}
|
||||
err := req.ReadEntity(createCategoryRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
category := &appv2.Category{}
|
||||
category.Name = createCategoryRequest.Name
|
||||
|
||||
if h.conflictedDone(req, resp, "category", category) {
|
||||
return
|
||||
}
|
||||
|
||||
MutateFn := func() error {
|
||||
if category.GetAnnotations() == nil {
|
||||
category.SetAnnotations(make(map[string]string))
|
||||
}
|
||||
annotations := createCategoryRequest.GetAnnotations()
|
||||
category.Annotations[constants.DisplayNameAnnotationKey] = annotations[constants.DisplayNameAnnotationKey]
|
||||
category.Annotations[constants.DescriptionAnnotationKey] = annotations[constants.DescriptionAnnotationKey]
|
||||
category.Spec.Icon = createCategoryRequest.Spec.Icon
|
||||
return nil
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(req.Request.Context(), h.client, category, MutateFn)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(category)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteCategory(req *restful.Request, resp *restful.Response) {
|
||||
categoryId := req.PathParameter("category")
|
||||
if categoryId == appv2.UncategorizedCategoryID {
|
||||
api.HandleBadRequest(resp, req, fmt.Errorf("%s is default Category can't be delete", appv2.UncategorizedCategoryID))
|
||||
return
|
||||
}
|
||||
|
||||
category := &appv2.Category{}
|
||||
err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: categoryId}, category)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if category.Status.Total > 0 {
|
||||
msg := fmt.Sprintf("can not delete helm category: %s which owns applications", categoryId)
|
||||
klog.Warningf(msg)
|
||||
api.HandleInternalError(resp, nil, errors.New(msg))
|
||||
return
|
||||
}
|
||||
err = h.client.Delete(req.Request.Context(), &appv2.Category{ObjectMeta: metav1.ObjectMeta{Name: categoryId}})
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeCategory(req *restful.Request, resp *restful.Response) {
|
||||
categoryId := req.PathParameter("category")
|
||||
|
||||
result := &appv2.Category{}
|
||||
err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: categoryId}, result)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
result.SetManagedFields(nil)
|
||||
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *appHandler) ListCategories(req *restful.Request, resp *restful.Response) {
|
||||
cList := &appv2.CategoryList{}
|
||||
err := h.client.List(req.Request.Context(), cList)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
resp.WriteEntity(convertToListResult(cList, req))
|
||||
}
|
||||
167
pkg/kapis/application/v2/handler_repo.go
Normal file
167
pkg/kapis/application/v2/handler_repo.go
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/klog/v2"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
)
|
||||
|
||||
func (h *appHandler) CreateOrUpdateRepo(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
repoRequest := &appv2.Repo{}
|
||||
err := req.ReadEntity(repoRequest)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
if repoRequest.Name == appv2.UploadRepoKey {
|
||||
api.HandleBadRequest(resp, req, fmt.Errorf("repo name %s is not allowed", appv2.UploadRepoKey))
|
||||
return
|
||||
}
|
||||
repoId := req.PathParameter("repo")
|
||||
if repoId == "" {
|
||||
repoId = repoRequest.Name
|
||||
}
|
||||
|
||||
parsedUrl, err := url.Parse(repoRequest.Spec.Url)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if parsedUrl.User != nil {
|
||||
repoRequest.Spec.Credential.Username = parsedUrl.User.Username()
|
||||
repoRequest.Spec.Credential.Password, _ = parsedUrl.User.Password()
|
||||
}
|
||||
|
||||
_, err = application.LoadRepoIndex(repoRequest.Spec.Url, repoRequest.Spec.Credential)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.QueryParameter("validate") != "" {
|
||||
data := map[string]any{"ok": true}
|
||||
resp.WriteAsJson(data)
|
||||
return
|
||||
}
|
||||
|
||||
repo := &appv2.Repo{}
|
||||
repo.Name = repoId
|
||||
if h.conflictedDone(req, resp, "repo", repo) {
|
||||
return
|
||||
}
|
||||
|
||||
mutateFn := func() error {
|
||||
repo.Spec = appv2.RepoSpec{
|
||||
Url: parsedUrl.String(),
|
||||
SyncPeriod: repoRequest.Spec.SyncPeriod,
|
||||
Description: stringutils.ShortenString(repoRequest.Spec.Description, 512),
|
||||
}
|
||||
if parsedUrl.User != nil {
|
||||
repo.Spec.Credential.Username = parsedUrl.User.Username()
|
||||
repo.Spec.Credential.Password, _ = parsedUrl.User.Password()
|
||||
}
|
||||
if repo.GetLabels() == nil {
|
||||
repo.SetLabels(map[string]string{})
|
||||
}
|
||||
repo.Labels[constants.WorkspaceLabelKey] = repoRequest.Labels[constants.WorkspaceLabelKey]
|
||||
|
||||
if repo.GetAnnotations() == nil {
|
||||
repo.SetAnnotations(map[string]string{})
|
||||
}
|
||||
ant := repoRequest.GetAnnotations()
|
||||
repo.Annotations[constants.DisplayNameAnnotationKey] = ant[constants.DisplayNameAnnotationKey]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(req.Request.Context(), h.client, repo, mutateFn)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{"repo_id": repoId}
|
||||
|
||||
resp.WriteAsJson(data)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteRepo(req *restful.Request, resp *restful.Response) {
|
||||
repoId := req.PathParameter("repo")
|
||||
|
||||
err := h.client.Delete(req.Request.Context(), &appv2.Repo{ObjectMeta: metav1.ObjectMeta{Name: repoId}})
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
klog.V(4).Info("delete repo: ", repoId)
|
||||
|
||||
resp.WriteEntity(errors.None)
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeRepo(req *restful.Request, resp *restful.Response) {
|
||||
repoId := req.PathParameter("repo")
|
||||
|
||||
key := runtimeclient.ObjectKey{Name: repoId}
|
||||
repo := &appv2.Repo{}
|
||||
err := h.client.Get(req.Request.Context(), key, repo)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
repo.SetManagedFields(nil)
|
||||
|
||||
resp.WriteEntity(repo)
|
||||
}
|
||||
|
||||
func (h *appHandler) ListRepos(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
helmRepoList := &appv2.RepoList{}
|
||||
err := h.client.List(req.Request.Context(), helmRepoList)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
workspace := req.PathParameter("workspace")
|
||||
filteredList := &appv2.RepoList{}
|
||||
for _, repo := range helmRepoList.Items {
|
||||
allowList := []string{appv2.SystemWorkspace, workspace}
|
||||
if !stringutils.StringIn(repo.Labels[constants.WorkspaceLabelKey], allowList) {
|
||||
continue
|
||||
}
|
||||
filteredList.Items = append(filteredList.Items, repo)
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(filteredList, req))
|
||||
}
|
||||
|
||||
func (h *appHandler) ListRepoEvents(req *restful.Request, resp *restful.Response) {
|
||||
repoId := req.PathParameter("repo")
|
||||
|
||||
list := v1.EventList{}
|
||||
selector := fields.SelectorFromSet(fields.Set{
|
||||
"involvedObject.name": repoId,
|
||||
})
|
||||
|
||||
opt := &runtimeclient.ListOptions{FieldSelector: selector}
|
||||
err := h.client.List(req.Request.Context(), &list, opt)
|
||||
if requestDone(err, resp) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(convertToListResult(&list, req))
|
||||
}
|
||||
151
pkg/kapis/application/v2/operator.go
Normal file
151
pkg/kapis/application/v2/operator.go
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
)
|
||||
|
||||
func (h *appHandler) AppCrList(req *restful.Request, resp *restful.Response) {
|
||||
clusterName := req.QueryParameter("cluster")
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: req.QueryParameter("group"),
|
||||
Version: req.QueryParameter("version"),
|
||||
Resource: req.QueryParameter("resource"),
|
||||
}
|
||||
opts := metav1.ListOptions{}
|
||||
labelSelectorStr := req.QueryParameter("labelSelector")
|
||||
labelSelector, err := labels.Parse(labelSelectorStr)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
opts.LabelSelector = labelSelector.String()
|
||||
_, dynamicClient, _, err := h.getCluster(clusterName)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
list, err := dynamicClient.Resource(gvr).List(context.Background(), opts)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
resp.WriteEntity(convertToListResult(list, req))
|
||||
}
|
||||
func checkPermissions(gvr schema.GroupVersionResource, app appv2.Application) (allow bool) {
|
||||
for _, i := range app.Spec.Resources {
|
||||
if gvr.Resource == i.Resource {
|
||||
allow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return allow
|
||||
}
|
||||
func (h *appHandler) CreateOrUpdateCR(req *restful.Request, resp *restful.Response) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: req.QueryParameter("group"),
|
||||
Version: req.QueryParameter("version"),
|
||||
Resource: req.QueryParameter("resource"),
|
||||
}
|
||||
appID := req.QueryParameter("app")
|
||||
app := appv2.Application{}
|
||||
err := h.client.Get(req.Request.Context(), client.ObjectKey{Name: appID}, &app)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
allow := checkPermissions(gvr, app)
|
||||
if !allow {
|
||||
api.HandleForbidden(resp, nil, fmt.Errorf("resource %s not allow", gvr.Resource))
|
||||
return
|
||||
}
|
||||
obj := unstructured.Unstructured{}
|
||||
err = req.ReadEntity(&obj)
|
||||
if err != nil {
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
js, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
clusterName := req.QueryParameter("cluster")
|
||||
_, dynamicClient, _, err := h.getCluster(clusterName)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
opt := metav1.PatchOptions{FieldManager: "applicationOperator"}
|
||||
_, err = dynamicClient.Resource(gvr).
|
||||
Namespace(obj.GetNamespace()).
|
||||
Patch(context.TODO(), obj.GetName(), types.ApplyPatchType, js, opt)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *appHandler) DescribeAppCr(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
name := req.PathParameter("crname")
|
||||
namespace := req.QueryParameter("namespace")
|
||||
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: req.QueryParameter("group"),
|
||||
Version: req.QueryParameter("version"),
|
||||
Resource: req.QueryParameter("resource"),
|
||||
}
|
||||
clusterName := req.QueryParameter("cluster")
|
||||
_, dynamicClient, _, err := h.getCluster(clusterName)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
get, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
get.SetManagedFields(nil)
|
||||
resp.WriteEntity(get)
|
||||
}
|
||||
|
||||
func (h *appHandler) DeleteAppCr(req *restful.Request, resp *restful.Response) {
|
||||
name := req.PathParameter("crname")
|
||||
namespace := req.QueryParameter("namespace")
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: req.QueryParameter("group"),
|
||||
Version: req.QueryParameter("version"),
|
||||
Resource: req.QueryParameter("resource"),
|
||||
}
|
||||
clusterName := req.QueryParameter("cluster")
|
||||
_, dynamicClient, _, err := h.getCluster(clusterName)
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
err = dynamicClient.Resource(gvr).Namespace(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
136
pkg/kapis/application/v2/register.go
Normal file
136
pkg/kapis/application/v2/register.go
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
|
||||
"kubesphere.io/utils/s3"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/rest"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
|
||||
)
|
||||
|
||||
type appHandler struct {
|
||||
client runtimeclient.Client
|
||||
clusterClient clusterclient.Interface
|
||||
s3opts *s3.Options
|
||||
ossStore s3.Interface
|
||||
cmStore s3.Interface
|
||||
}
|
||||
|
||||
func NewHandler(cacheClient runtimeclient.Client, clusterClient clusterclient.Interface, s3opts *s3.Options) rest.Handler {
|
||||
handler := &appHandler{
|
||||
client: cacheClient,
|
||||
clusterClient: clusterClient,
|
||||
s3opts: s3opts,
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
func NewFakeHandler() rest.Handler {
|
||||
return &appHandler{}
|
||||
}
|
||||
|
||||
type funcInfo struct {
|
||||
Route string
|
||||
Func func(req *restful.Request, resp *restful.Response)
|
||||
Method func(subPath string) *restful.RouteBuilder
|
||||
Doc string
|
||||
NeedGlobal bool
|
||||
Workspace bool
|
||||
Namespace bool
|
||||
Params []*restful.Parameter
|
||||
}
|
||||
|
||||
func (h *appHandler) AddToContainer(c *restful.Container) (err error) {
|
||||
ws := runtime.NewWebService(appv2.SchemeGroupVersion)
|
||||
if h.cmStore, h.ossStore, err = application.InitStore(h.s3opts, h.client); err != nil {
|
||||
klog.Errorf("failed to init store: %v", err)
|
||||
return err
|
||||
}
|
||||
funcInfoList := []funcInfo{
|
||||
{Route: "/repos", Func: h.ListRepos, Method: ws.GET, Workspace: true},
|
||||
{Route: "/repos", Func: h.CreateOrUpdateRepo, Method: ws.POST, Workspace: true},
|
||||
{Route: "/repos/{repo}", Func: h.CreateOrUpdateRepo, Method: ws.PATCH, Workspace: true},
|
||||
{Route: "/repos/{repo}", Func: h.DeleteRepo, Method: ws.DELETE, Workspace: true},
|
||||
{Route: "/repos/{repo}", Func: h.DescribeRepo, Method: ws.GET, Workspace: true},
|
||||
{Route: "/repos/{repo}/events", Func: h.ListRepoEvents, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/action", Func: h.DoAppAction, Method: ws.POST},
|
||||
{Route: "/apps", Func: h.CreateOrUpdateApp, Method: ws.POST, Workspace: true},
|
||||
{Route: "/apps", Func: h.ListApps, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}", Func: h.CreateOrUpdateApp, Method: ws.POST, Workspace: true},
|
||||
{Route: "/apps/{app}", Func: h.PatchApp, Method: ws.PATCH, Workspace: true},
|
||||
{Route: "/apps/{app}", Func: h.DescribeApp, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}", Func: h.DeleteApp, Method: ws.DELETE, Workspace: true},
|
||||
{Route: "/apps/{app}/versions", Func: h.CreateOrUpdateAppVersion, Method: ws.POST, Workspace: true},
|
||||
{Route: "/apps/{app}/versions", Func: h.ListAppVersions, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}", Func: h.DeleteAppVersion, Method: ws.DELETE, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}", Func: h.DescribeAppVersion, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}", Func: h.CreateOrUpdateAppVersion, Method: ws.POST, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}/package", Func: h.GetAppVersionPackage, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}/files", Func: h.GetAppVersionFiles, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/versions/{version}/action", Func: h.AppVersionAction, Method: ws.POST, Workspace: true},
|
||||
{Route: "/applications", Func: h.ListAppRls, Method: ws.GET, Workspace: true, Namespace: true},
|
||||
{Route: "/applications", Func: h.CreateOrUpdateAppRls, Method: ws.POST, Workspace: true, Namespace: true},
|
||||
{Route: "/applications/{application}", Func: h.CreateOrUpdateAppRls, Method: ws.POST, Namespace: true},
|
||||
{Route: "/applications/{application}", Func: h.DescribeAppRls, Method: ws.GET, Namespace: true},
|
||||
{Route: "/applications/{application}", Func: h.DeleteAppRls, Method: ws.DELETE, Namespace: true},
|
||||
{Route: "/categories", Func: h.CreateOrUpdateCategory, Method: ws.POST},
|
||||
{Route: "/categories", Func: h.ListCategories, Method: ws.GET},
|
||||
{Route: "/categories/{category}", Func: h.DeleteCategory, Method: ws.DELETE},
|
||||
{Route: "/categories/{category}", Func: h.CreateOrUpdateCategory, Method: ws.POST},
|
||||
{Route: "/categories/{category}", Func: h.DescribeCategory, Method: ws.GET},
|
||||
{Route: "/reviews", Func: h.ListReviews, Method: ws.GET},
|
||||
{Route: "/attachments", Func: h.CreateAttachment, Method: ws.POST, Workspace: true},
|
||||
{Route: "/attachments/{attachment}", Func: h.DescribeAttachment, Method: ws.GET, Workspace: true},
|
||||
{Route: "/attachments/{attachment}", Func: h.DeleteAttachments, Method: ws.DELETE, Workspace: true},
|
||||
{Route: "/apps/{app}/examplecr/{name}", Func: h.exampleCr, Method: ws.GET, Workspace: true},
|
||||
{Route: "/apps/{app}/cr", Func: h.AppCrList, Method: ws.GET, Workspace: true, Namespace: true},
|
||||
{Route: "/cr", Func: h.CreateOrUpdateCR, Method: ws.POST, Workspace: true, Namespace: true},
|
||||
{Route: "/cr/{crname}", Func: h.DescribeAppCr, Method: ws.GET, Workspace: true, Namespace: true},
|
||||
{Route: "/cr/{crname}", Func: h.DeleteAppCr, Method: ws.DELETE, Workspace: true, Namespace: true},
|
||||
}
|
||||
for _, info := range funcInfoList {
|
||||
builder := info.Method(info.Route).To(info.Func).Doc(info.Doc)
|
||||
for _, param := range info.Params {
|
||||
builder = builder.Param(param)
|
||||
}
|
||||
ws.Route(builder)
|
||||
|
||||
if info.Workspace {
|
||||
workspaceRoute := fmt.Sprintf("/workspaces/{workspace}%s", info.Route)
|
||||
builder = info.Method(workspaceRoute).To(info.Func).Doc(info.Doc)
|
||||
for _, param := range info.Params {
|
||||
builder = builder.Param(param)
|
||||
}
|
||||
builder.Param(ws.PathParameter("workspace", "workspace"))
|
||||
ws.Route(builder)
|
||||
}
|
||||
|
||||
if info.Namespace {
|
||||
namespaceRoute := fmt.Sprintf("/namespaces/{namespace}%s", info.Route)
|
||||
builder = info.Method(namespaceRoute).To(info.Func).Doc(info.Doc)
|
||||
for _, param := range info.Params {
|
||||
builder = builder.Param(param)
|
||||
}
|
||||
builder.Param(ws.PathParameter("namespace", "namespace"))
|
||||
ws.Route(builder)
|
||||
}
|
||||
}
|
||||
|
||||
c.Add(ws)
|
||||
return nil
|
||||
}
|
||||
166
pkg/kapis/application/v2/utils.go
Normal file
166
pkg/kapis/application/v2/utils.go
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"k8s.io/client-go/dynamic"
|
||||
appv2 "kubesphere.io/api/application/v2"
|
||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/application"
|
||||
)
|
||||
|
||||
const (
|
||||
Status = "status"
|
||||
)
|
||||
|
||||
func parseRequest(createRequest application.AppRequest) (appRequest application.AppRequest, err error) {
|
||||
if createRequest.AppType == appv2.AppTypeHelm {
|
||||
req, err := parseHelmRequest(createRequest)
|
||||
return req, err
|
||||
}
|
||||
_, err = application.ReadYaml(createRequest.Package)
|
||||
|
||||
return createRequest, err
|
||||
}
|
||||
|
||||
func parseHelmRequest(createRequest application.AppRequest) (req application.AppRequest, err error) {
|
||||
if createRequest.Package == nil || len(createRequest.Package) == 0 {
|
||||
return req, errors.New("package is empty")
|
||||
}
|
||||
chartPack, err := loader.LoadArchive(bytes.NewReader(createRequest.Package))
|
||||
if err != nil {
|
||||
return createRequest, err
|
||||
}
|
||||
|
||||
crdFiles := chartPack.CRDObjects()
|
||||
for _, i := range crdFiles {
|
||||
dataList, err := readYaml(i.File.Data)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to read %s yaml: %v", i.Filename, err)
|
||||
return createRequest, err
|
||||
}
|
||||
for _, d := range dataList {
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(d), 1024)
|
||||
crd := v1.CustomResourceDefinition{}
|
||||
err = decoder.Decode(&crd)
|
||||
if err != nil {
|
||||
klog.Error(err, "Failed to decode crd file")
|
||||
return application.AppRequest{}, err
|
||||
}
|
||||
var servedVersion *v1.CustomResourceDefinitionVersion
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Served && v.Storage {
|
||||
servedVersion = &v
|
||||
}
|
||||
}
|
||||
if servedVersion == nil {
|
||||
klog.Warningf("no served and storage version found in crd %s", crd.Name)
|
||||
continue
|
||||
}
|
||||
ins := appv2.GroupVersionResource{
|
||||
Group: crd.Spec.Group,
|
||||
Version: servedVersion.Name,
|
||||
Resource: crd.Spec.Names.Plural,
|
||||
}
|
||||
createRequest.Resources = append(createRequest.Resources, ins)
|
||||
}
|
||||
}
|
||||
|
||||
shortName := application.GenerateShortNameMD5Hash(chartPack.Metadata.Name)
|
||||
fillEmptyFields(&createRequest, chartPack, shortName)
|
||||
|
||||
return createRequest, nil
|
||||
}
|
||||
|
||||
func readYaml(data []byte) (yamlList [][]byte, err error) {
|
||||
// Read yaml file which has multi line yaml and split it into a list of yaml documents.
|
||||
r := yaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(data)))
|
||||
for {
|
||||
d, err := r.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("failed to read yaml: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
//skip empty yaml or empty ---
|
||||
if len(strings.TrimSpace(string(d))) > 3 {
|
||||
yamlList = append(yamlList, d)
|
||||
}
|
||||
}
|
||||
return yamlList, nil
|
||||
}
|
||||
|
||||
func fillEmptyFields(createRequest *application.AppRequest, chartPack *chart.Chart, shortName string) {
|
||||
if createRequest.AppName == "" {
|
||||
createRequest.AppName = application.GetUuid36(shortName + "-")
|
||||
}
|
||||
if createRequest.OriginalName == "" {
|
||||
createRequest.OriginalName = chartPack.Metadata.Name
|
||||
}
|
||||
if createRequest.AppHome == "" {
|
||||
createRequest.AppHome = chartPack.Metadata.Home
|
||||
}
|
||||
if createRequest.Icon == "" {
|
||||
createRequest.Icon = chartPack.Metadata.Icon
|
||||
}
|
||||
if createRequest.VersionName == "" {
|
||||
createRequest.VersionName = chartPack.Metadata.Version
|
||||
}
|
||||
|
||||
if createRequest.Maintainers == nil || len(createRequest.Maintainers) == 0 {
|
||||
createRequest.Maintainers = application.GetMaintainers(chartPack.Metadata.Maintainers)
|
||||
}
|
||||
if createRequest.RepoName == "" {
|
||||
createRequest.RepoName = appv2.UploadRepoKey
|
||||
}
|
||||
if createRequest.Description == "" {
|
||||
createRequest.Description = chartPack.Metadata.Description
|
||||
}
|
||||
if createRequest.Abstraction == "" {
|
||||
createRequest.Abstraction = chartPack.Metadata.Description
|
||||
}
|
||||
if createRequest.AliasName == "" {
|
||||
createRequest.AliasName = chartPack.Metadata.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (h *appHandler) getCluster(clusterName string) (runtimeclient.Client, *dynamic.DynamicClient, *clusterv1alpha1.Cluster, error) {
|
||||
klog.Infof("get cluster %s", clusterName)
|
||||
runtimeClient, err := h.clusterClient.GetRuntimeClient(clusterName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
clusterClient, err := h.clusterClient.GetClusterClient(clusterName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(clusterClient.RestConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
cluster, err := h.clusterClient.Get(clusterName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return runtimeClient, dynamicClient, cluster, nil
|
||||
}
|
||||
Reference in New Issue
Block a user